mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 21:00:58 +03:00
Merge branch 'main' into feature/LlamaIndex
# Conflicts: # packages/components/nodes/agents/ConversationalAgent/ConversationalAgent.ts # packages/components/nodes/agents/ConversationalRetrievalAgent/ConversationalRetrievalAgent.ts # packages/components/nodes/agents/OpenAIAssistant/OpenAIAssistant.ts # packages/components/nodes/agents/OpenAIFunctionAgent/OpenAIFunctionAgent.ts # packages/components/nodes/chains/ConversationChain/ConversationChain.ts # packages/components/nodes/chains/ConversationalRetrievalQAChain/ConversationalRetrievalQAChain.ts # packages/components/nodes/memory/BufferMemory/BufferMemory.ts # packages/components/nodes/memory/BufferWindowMemory/BufferWindowMemory.ts # packages/components/nodes/memory/ConversationSummaryMemory/ConversationSummaryMemory.ts # packages/components/nodes/memory/DynamoDb/DynamoDb.ts # packages/components/nodes/memory/MongoDBMemory/MongoDBMemory.ts # packages/components/nodes/memory/MotorheadMemory/MotorheadMemory.ts # packages/components/nodes/memory/RedisBackedChatMemory/RedisBackedChatMemory.ts # packages/components/nodes/memory/UpstashRedisBackedChatMemory/UpstashRedisBackedChatMemory.ts # packages/components/nodes/memory/ZepMemory/ZepMemory.ts # packages/components/src/utils.ts # packages/server/marketplaces/chatflows/Long Term Memory.json # packages/server/src/index.ts # packages/server/src/utils/index.ts
This commit is contained in:
@@ -29,6 +29,12 @@ export interface ICommonObject {
|
||||
[key: string]: any | CommonType | ICommonObject | CommonType[] | ICommonObject[]
|
||||
}
|
||||
|
||||
export interface IVariable {
|
||||
name: string
|
||||
value: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export type IDatabaseEntity = {
|
||||
[key: string]: any
|
||||
}
|
||||
@@ -73,6 +79,7 @@ export interface INodeParams {
|
||||
additionalParams?: boolean
|
||||
loadMethod?: string
|
||||
hidden?: boolean
|
||||
variables?: ICommonObject[]
|
||||
}
|
||||
|
||||
export interface INodeExecutionData {
|
||||
@@ -89,7 +96,7 @@ export interface INodeProperties {
|
||||
type: string
|
||||
icon: string
|
||||
version: number
|
||||
category: string
|
||||
category: string // TODO: use enum instead of string
|
||||
baseClasses: string[]
|
||||
tags?: string[]
|
||||
description?: string
|
||||
@@ -192,3 +199,45 @@ export class VectorStoreRetriever {
|
||||
this.vectorStore = fields.vectorStore
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement abstract classes and interface for memory
|
||||
*/
|
||||
import { BaseMessage } from 'langchain/schema'
|
||||
import { BufferMemory, BufferWindowMemory, ConversationSummaryMemory } from 'langchain/memory'
|
||||
|
||||
export interface MemoryMethods {
|
||||
getChatMessages(overrideSessionId?: string, returnBaseMessages?: boolean, prevHistory?: IMessage[]): Promise<IMessage[] | BaseMessage[]>
|
||||
addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
|
||||
clearChatMessages(overrideSessionId?: string): Promise<void>
|
||||
}
|
||||
|
||||
export abstract class FlowiseMemory extends BufferMemory implements MemoryMethods {
|
||||
abstract getChatMessages(
|
||||
overrideSessionId?: string,
|
||||
returnBaseMessages?: boolean,
|
||||
prevHistory?: IMessage[]
|
||||
): Promise<IMessage[] | BaseMessage[]>
|
||||
abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
|
||||
abstract clearChatMessages(overrideSessionId?: string): Promise<void>
|
||||
}
|
||||
|
||||
export abstract class FlowiseWindowMemory extends BufferWindowMemory implements MemoryMethods {
|
||||
abstract getChatMessages(
|
||||
overrideSessionId?: string,
|
||||
returnBaseMessages?: boolean,
|
||||
prevHistory?: IMessage[]
|
||||
): Promise<IMessage[] | BaseMessage[]>
|
||||
abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
|
||||
abstract clearChatMessages(overrideSessionId?: string): Promise<void>
|
||||
}
|
||||
|
||||
export abstract class FlowiseSummaryMemory extends ConversationSummaryMemory implements MemoryMethods {
|
||||
abstract getChatMessages(
|
||||
overrideSessionId?: string,
|
||||
returnBaseMessages?: boolean,
|
||||
prevHistory?: IMessage[]
|
||||
): Promise<IMessage[] | BaseMessage[]>
|
||||
abstract addChatMessages(msgArray: { text: string; type: MessageType }[], overrideSessionId?: string): Promise<void>
|
||||
abstract clearChatMessages(overrideSessionId?: string): Promise<void>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
import { AgentExecutorInput, BaseSingleActionAgent, BaseMultiActionAgent, RunnableAgent, StoppingMethod } from 'langchain/agents'
|
||||
import { ChainValues, AgentStep, AgentFinish, AgentAction, BaseMessage, FunctionMessage, AIMessage } from 'langchain/schema'
|
||||
import { OutputParserException } from 'langchain/schema/output_parser'
|
||||
import { CallbackManager, CallbackManagerForChainRun, Callbacks } from 'langchain/callbacks'
|
||||
import { ToolInputParsingException, Tool } from '@langchain/core/tools'
|
||||
import { Runnable } from 'langchain/schema/runnable'
|
||||
import { BaseChain, SerializedLLMChain } from 'langchain/chains'
|
||||
import { Serializable } from '@langchain/core/load/serializable'
|
||||
|
||||
type AgentExecutorOutput = ChainValues
|
||||
|
||||
interface AgentExecutorIteratorInput {
|
||||
agentExecutor: AgentExecutor
|
||||
inputs: Record<string, string>
|
||||
callbacks?: Callbacks
|
||||
tags?: string[]
|
||||
metadata?: Record<string, unknown>
|
||||
runName?: string
|
||||
runManager?: CallbackManagerForChainRun
|
||||
}
|
||||
|
||||
//TODO: stream tools back
|
||||
export class AgentExecutorIterator extends Serializable implements AgentExecutorIteratorInput {
|
||||
lc_namespace = ['langchain', 'agents', 'executor_iterator']
|
||||
|
||||
agentExecutor: AgentExecutor
|
||||
|
||||
inputs: Record<string, string>
|
||||
|
||||
callbacks: Callbacks
|
||||
|
||||
tags: string[] | undefined
|
||||
|
||||
metadata: Record<string, unknown> | undefined
|
||||
|
||||
runName: string | undefined
|
||||
|
||||
private _finalOutputs: Record<string, unknown> | undefined
|
||||
|
||||
get finalOutputs(): Record<string, unknown> | undefined {
|
||||
return this._finalOutputs
|
||||
}
|
||||
|
||||
/** Intended to be used as a setter method, needs to be async. */
|
||||
async setFinalOutputs(value: Record<string, unknown> | undefined) {
|
||||
this._finalOutputs = undefined
|
||||
if (value) {
|
||||
const preparedOutputs: Record<string, unknown> = await this.agentExecutor.prepOutputs(this.inputs, value, true)
|
||||
this._finalOutputs = preparedOutputs
|
||||
}
|
||||
}
|
||||
|
||||
runManager: CallbackManagerForChainRun | undefined
|
||||
|
||||
intermediateSteps: AgentStep[] = []
|
||||
|
||||
iterations = 0
|
||||
|
||||
get nameToToolMap(): Record<string, Tool> {
|
||||
const toolMap = this.agentExecutor.tools.map((tool) => ({
|
||||
[tool.name]: tool
|
||||
}))
|
||||
return Object.assign({}, ...toolMap)
|
||||
}
|
||||
|
||||
constructor(fields: AgentExecutorIteratorInput) {
|
||||
super(fields)
|
||||
this.agentExecutor = fields.agentExecutor
|
||||
this.inputs = fields.inputs
|
||||
this.tags = fields.tags
|
||||
this.metadata = fields.metadata
|
||||
this.runName = fields.runName
|
||||
this.runManager = fields.runManager
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the iterator to its initial state, clearing intermediate steps,
|
||||
* iterations, and the final output.
|
||||
*/
|
||||
reset(): void {
|
||||
this.intermediateSteps = []
|
||||
this.iterations = 0
|
||||
this._finalOutputs = undefined
|
||||
}
|
||||
|
||||
updateIterations(): void {
|
||||
this.iterations += 1
|
||||
}
|
||||
|
||||
async *streamIterator() {
|
||||
this.reset()
|
||||
|
||||
// Loop to handle iteration
|
||||
while (true) {
|
||||
try {
|
||||
if (this.iterations === 0) {
|
||||
await this.onFirstStep()
|
||||
}
|
||||
|
||||
const result = await this._callNext()
|
||||
yield result
|
||||
} catch (e: any) {
|
||||
if ('message' in e && e.message.startsWith('Final outputs already reached: ')) {
|
||||
if (!this.finalOutputs) {
|
||||
throw e
|
||||
}
|
||||
return this.finalOutputs
|
||||
}
|
||||
if (this.runManager) {
|
||||
await this.runManager.handleChainError(e)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform any necessary setup for the first step
|
||||
* of the asynchronous iterator.
|
||||
*/
|
||||
async onFirstStep(): Promise<void> {
|
||||
if (this.iterations === 0) {
|
||||
const callbackManager = await CallbackManager.configure(
|
||||
this.callbacks,
|
||||
this.agentExecutor.callbacks,
|
||||
this.tags,
|
||||
this.agentExecutor.tags,
|
||||
this.metadata,
|
||||
this.agentExecutor.metadata,
|
||||
{
|
||||
verbose: this.agentExecutor.verbose
|
||||
}
|
||||
)
|
||||
this.runManager = await callbackManager?.handleChainStart(
|
||||
this.agentExecutor.toJSON(),
|
||||
this.inputs,
|
||||
undefined,
|
||||
undefined,
|
||||
this.tags,
|
||||
this.metadata,
|
||||
this.runName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the next step in the chain using the
|
||||
* AgentExecutor's _takeNextStep method.
|
||||
*/
|
||||
async _executeNextStep(runManager?: CallbackManagerForChainRun): Promise<AgentFinish | AgentStep[]> {
|
||||
return this.agentExecutor._takeNextStep(this.nameToToolMap, this.inputs, this.intermediateSteps, runManager)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the output of the next step,
|
||||
* handling AgentFinish and tool return cases.
|
||||
*/
|
||||
async _processNextStepOutput(
|
||||
nextStepOutput: AgentFinish | AgentStep[],
|
||||
runManager?: CallbackManagerForChainRun
|
||||
): Promise<Record<string, string | AgentStep[]>> {
|
||||
if ('returnValues' in nextStepOutput) {
|
||||
const output = await this.agentExecutor._return(nextStepOutput as AgentFinish, this.intermediateSteps, runManager)
|
||||
if (this.runManager) {
|
||||
await this.runManager.handleChainEnd(output)
|
||||
}
|
||||
await this.setFinalOutputs(output)
|
||||
return output
|
||||
}
|
||||
|
||||
this.intermediateSteps = this.intermediateSteps.concat(nextStepOutput as AgentStep[])
|
||||
|
||||
let output: Record<string, string | AgentStep[]> = {}
|
||||
if (Array.isArray(nextStepOutput) && nextStepOutput.length === 1) {
|
||||
const nextStep = nextStepOutput[0]
|
||||
const toolReturn = await this.agentExecutor._getToolReturn(nextStep)
|
||||
if (toolReturn) {
|
||||
output = await this.agentExecutor._return(toolReturn, this.intermediateSteps, runManager)
|
||||
if (this.runManager) {
|
||||
await this.runManager.handleChainEnd(output)
|
||||
}
|
||||
await this.setFinalOutputs(output)
|
||||
}
|
||||
}
|
||||
output = { intermediateSteps: nextStepOutput as AgentStep[] }
|
||||
return output
|
||||
}
|
||||
|
||||
async _stop(): Promise<Record<string, unknown>> {
|
||||
const output = await this.agentExecutor.agent.returnStoppedResponse(
|
||||
this.agentExecutor.earlyStoppingMethod,
|
||||
this.intermediateSteps,
|
||||
this.inputs
|
||||
)
|
||||
const returnedOutput = await this.agentExecutor._return(output, this.intermediateSteps, this.runManager)
|
||||
await this.setFinalOutputs(returnedOutput)
|
||||
return returnedOutput
|
||||
}
|
||||
|
||||
async _callNext(): Promise<Record<string, unknown>> {
|
||||
// final output already reached: stopiteration (final output)
|
||||
if (this.finalOutputs) {
|
||||
throw new Error(`Final outputs already reached: ${JSON.stringify(this.finalOutputs, null, 2)}`)
|
||||
}
|
||||
// timeout/max iterations: stopiteration (stopped response)
|
||||
if (!this.agentExecutor.shouldContinueGetter(this.iterations)) {
|
||||
return this._stop()
|
||||
}
|
||||
const nextStepOutput = await this._executeNextStep(this.runManager)
|
||||
const output = await this._processNextStepOutput(nextStepOutput, this.runManager)
|
||||
this.updateIterations()
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentExecutor extends BaseChain<ChainValues, AgentExecutorOutput> {
|
||||
static lc_name() {
|
||||
return 'AgentExecutor'
|
||||
}
|
||||
|
||||
get lc_namespace() {
|
||||
return ['langchain', 'agents', 'executor']
|
||||
}
|
||||
|
||||
agent: BaseSingleActionAgent | BaseMultiActionAgent
|
||||
|
||||
tools: this['agent']['ToolType'][]
|
||||
|
||||
returnIntermediateSteps = false
|
||||
|
||||
maxIterations?: number = 15
|
||||
|
||||
earlyStoppingMethod: StoppingMethod = 'force'
|
||||
|
||||
sessionId?: string
|
||||
|
||||
chatId?: string
|
||||
|
||||
input?: string
|
||||
|
||||
/**
|
||||
* How to handle errors raised by the agent's output parser.
|
||||
Defaults to `False`, which raises the error.
|
||||
|
||||
If `true`, the error will be sent back to the LLM as an observation.
|
||||
If a string, the string itself will be sent to the LLM as an observation.
|
||||
If a callable function, the function will be called with the exception
|
||||
as an argument, and the result of that function will be passed to the agent
|
||||
as an observation.
|
||||
*/
|
||||
handleParsingErrors: boolean | string | ((e: OutputParserException | ToolInputParsingException) => string) = false
|
||||
|
||||
get inputKeys() {
|
||||
return this.agent.inputKeys
|
||||
}
|
||||
|
||||
get outputKeys() {
|
||||
return this.agent.returnValues
|
||||
}
|
||||
|
||||
constructor(input: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }) {
|
||||
let agent: BaseSingleActionAgent | BaseMultiActionAgent
|
||||
if (Runnable.isRunnable(input.agent)) {
|
||||
agent = new RunnableAgent({ runnable: input.agent })
|
||||
} else {
|
||||
agent = input.agent
|
||||
}
|
||||
|
||||
super(input)
|
||||
this.agent = agent
|
||||
this.tools = input.tools
|
||||
this.handleParsingErrors = input.handleParsingErrors ?? this.handleParsingErrors
|
||||
/* Getting rid of this because RunnableAgent doesnt allow return direct
|
||||
if (this.agent._agentActionType() === "multi") {
|
||||
for (const tool of this.tools) {
|
||||
if (tool.returnDirect) {
|
||||
throw new Error(
|
||||
`Tool with return direct ${tool.name} not supported for multi-action agent.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
this.returnIntermediateSteps = input.returnIntermediateSteps ?? this.returnIntermediateSteps
|
||||
this.maxIterations = input.maxIterations ?? this.maxIterations
|
||||
this.earlyStoppingMethod = input.earlyStoppingMethod ?? this.earlyStoppingMethod
|
||||
this.sessionId = input.sessionId
|
||||
this.chatId = input.chatId
|
||||
this.input = input.input
|
||||
}
|
||||
|
||||
static fromAgentAndTools(fields: AgentExecutorInput & { sessionId?: string; chatId?: string; input?: string }): AgentExecutor {
|
||||
const newInstance = new AgentExecutor(fields)
|
||||
if (fields.sessionId) newInstance.sessionId = fields.sessionId
|
||||
if (fields.chatId) newInstance.chatId = fields.chatId
|
||||
if (fields.input) newInstance.input = fields.input
|
||||
return newInstance
|
||||
}
|
||||
|
||||
get shouldContinueGetter() {
|
||||
return this.shouldContinue.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Method that checks if the agent execution should continue based on the
|
||||
* number of iterations.
|
||||
* @param iterations The current number of iterations.
|
||||
* @returns A boolean indicating whether the agent execution should continue.
|
||||
*/
|
||||
private shouldContinue(iterations: number): boolean {
|
||||
return this.maxIterations === undefined || iterations < this.maxIterations
|
||||
}
|
||||
|
||||
async _call(inputs: ChainValues, runManager?: CallbackManagerForChainRun): Promise<AgentExecutorOutput> {
|
||||
const toolsByName = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t]))
|
||||
|
||||
const steps: AgentStep[] = []
|
||||
let iterations = 0
|
||||
|
||||
const getOutput = async (finishStep: AgentFinish): Promise<AgentExecutorOutput> => {
|
||||
const { returnValues } = finishStep
|
||||
const additional = await this.agent.prepareForOutput(returnValues, steps)
|
||||
|
||||
if (this.returnIntermediateSteps) {
|
||||
return { ...returnValues, intermediateSteps: steps, ...additional }
|
||||
}
|
||||
await runManager?.handleAgentEnd(finishStep)
|
||||
return { ...returnValues, ...additional }
|
||||
}
|
||||
|
||||
while (this.shouldContinue(iterations)) {
|
||||
let output
|
||||
try {
|
||||
output = await this.agent.plan(steps, inputs, runManager?.getChild())
|
||||
} catch (e) {
|
||||
if (e instanceof OutputParserException) {
|
||||
let observation
|
||||
let text = e.message
|
||||
if (this.handleParsingErrors === true) {
|
||||
if (e.sendToLLM) {
|
||||
observation = e.observation
|
||||
text = e.llmOutput ?? ''
|
||||
} else {
|
||||
observation = 'Invalid or incomplete response'
|
||||
}
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
output = {
|
||||
tool: '_Exception',
|
||||
toolInput: observation,
|
||||
log: text
|
||||
} as AgentAction
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
// Check if the agent has finished
|
||||
if ('returnValues' in output) {
|
||||
return getOutput(output)
|
||||
}
|
||||
|
||||
let actions: AgentAction[]
|
||||
if (Array.isArray(output)) {
|
||||
actions = output as AgentAction[]
|
||||
} else {
|
||||
actions = [output as AgentAction]
|
||||
}
|
||||
|
||||
const newSteps = await Promise.all(
|
||||
actions.map(async (action) => {
|
||||
await runManager?.handleAgentAction(action)
|
||||
const tool = action.tool === '_Exception' ? new ExceptionTool() : toolsByName[action.tool?.toLowerCase()]
|
||||
let observation
|
||||
try {
|
||||
/* Here we need to override Tool call method to include sessionId, chatId, input as parameter
|
||||
* Tool Call Parameters:
|
||||
* - arg: z.output<T>
|
||||
* - configArg?: RunnableConfig | Callbacks
|
||||
* - tags?: string[]
|
||||
* - flowConfig?: { sessionId?: string, chatId?: string, input?: string }
|
||||
*/
|
||||
observation = tool
|
||||
? // @ts-ignore
|
||||
await tool.call(action.toolInput, runManager?.getChild(), undefined, {
|
||||
sessionId: this.sessionId,
|
||||
chatId: this.chatId,
|
||||
input: this.input
|
||||
})
|
||||
: `${action.tool} is not a valid tool, try another one.`
|
||||
} catch (e) {
|
||||
if (e instanceof ToolInputParsingException) {
|
||||
if (this.handleParsingErrors === true) {
|
||||
observation = 'Invalid or incomplete tool input. Please try again.'
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
observation = await new ExceptionTool().call(observation, runManager?.getChild())
|
||||
return { action, observation: observation ?? '' }
|
||||
}
|
||||
}
|
||||
return { action, observation: observation ?? '' }
|
||||
})
|
||||
)
|
||||
|
||||
steps.push(...newSteps)
|
||||
|
||||
const lastStep = steps[steps.length - 1]
|
||||
const lastTool = toolsByName[lastStep.action.tool?.toLowerCase()]
|
||||
|
||||
if (lastTool?.returnDirect) {
|
||||
return getOutput({
|
||||
returnValues: { [this.agent.returnValues[0]]: lastStep.observation },
|
||||
log: ''
|
||||
})
|
||||
}
|
||||
|
||||
iterations += 1
|
||||
}
|
||||
|
||||
const finish = await this.agent.returnStoppedResponse(this.earlyStoppingMethod, steps, inputs)
|
||||
|
||||
return getOutput(finish)
|
||||
}
|
||||
|
||||
async _takeNextStep(
|
||||
nameToolMap: Record<string, Tool>,
|
||||
inputs: ChainValues,
|
||||
intermediateSteps: AgentStep[],
|
||||
runManager?: CallbackManagerForChainRun
|
||||
): Promise<AgentFinish | AgentStep[]> {
|
||||
let output
|
||||
try {
|
||||
output = await this.agent.plan(intermediateSteps, inputs, runManager?.getChild())
|
||||
} catch (e) {
|
||||
if (e instanceof OutputParserException) {
|
||||
let observation
|
||||
let text = e.message
|
||||
if (this.handleParsingErrors === true) {
|
||||
if (e.sendToLLM) {
|
||||
observation = e.observation
|
||||
text = e.llmOutput ?? ''
|
||||
} else {
|
||||
observation = 'Invalid or incomplete response'
|
||||
}
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
output = {
|
||||
tool: '_Exception',
|
||||
toolInput: observation,
|
||||
log: text
|
||||
} as AgentAction
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
if ('returnValues' in output) {
|
||||
return output
|
||||
}
|
||||
|
||||
let actions: AgentAction[]
|
||||
if (Array.isArray(output)) {
|
||||
actions = output as AgentAction[]
|
||||
} else {
|
||||
actions = [output as AgentAction]
|
||||
}
|
||||
|
||||
const result: AgentStep[] = []
|
||||
for (const agentAction of actions) {
|
||||
let observation = ''
|
||||
if (runManager) {
|
||||
await runManager?.handleAgentAction(agentAction)
|
||||
}
|
||||
if (agentAction.tool in nameToolMap) {
|
||||
const tool = nameToolMap[agentAction.tool]
|
||||
try {
|
||||
/* Here we need to override Tool call method to include sessionId, chatId, input as parameter
|
||||
* Tool Call Parameters:
|
||||
* - arg: z.output<T>
|
||||
* - configArg?: RunnableConfig | Callbacks
|
||||
* - tags?: string[]
|
||||
* - flowConfig?: { sessionId?: string, chatId?: string, input?: string }
|
||||
*/
|
||||
// @ts-ignore
|
||||
observation = await tool.call(agentAction.toolInput, runManager?.getChild(), undefined, {
|
||||
sessionId: this.sessionId,
|
||||
chatId: this.chatId,
|
||||
input: this.input
|
||||
})
|
||||
} catch (e) {
|
||||
if (e instanceof ToolInputParsingException) {
|
||||
if (this.handleParsingErrors === true) {
|
||||
observation = 'Invalid or incomplete tool input. Please try again.'
|
||||
} else if (typeof this.handleParsingErrors === 'string') {
|
||||
observation = this.handleParsingErrors
|
||||
} else if (typeof this.handleParsingErrors === 'function') {
|
||||
observation = this.handleParsingErrors(e)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
observation = await new ExceptionTool().call(observation, runManager?.getChild())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
observation = `${agentAction.tool} is not a valid tool, try another available tool: ${Object.keys(nameToolMap).join(', ')}`
|
||||
}
|
||||
result.push({
|
||||
action: agentAction,
|
||||
observation
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async _return(
|
||||
output: AgentFinish,
|
||||
intermediateSteps: AgentStep[],
|
||||
runManager?: CallbackManagerForChainRun
|
||||
): Promise<AgentExecutorOutput> {
|
||||
if (runManager) {
|
||||
await runManager.handleAgentEnd(output)
|
||||
}
|
||||
const finalOutput: Record<string, unknown> = output.returnValues
|
||||
if (this.returnIntermediateSteps) {
|
||||
finalOutput.intermediateSteps = intermediateSteps
|
||||
}
|
||||
return finalOutput
|
||||
}
|
||||
|
||||
async _getToolReturn(nextStepOutput: AgentStep): Promise<AgentFinish | null> {
|
||||
const { action, observation } = nextStepOutput
|
||||
const nameToolMap = Object.fromEntries(this.tools.map((t) => [t.name.toLowerCase(), t]))
|
||||
const [returnValueKey = 'output'] = this.agent.returnValues
|
||||
// Invalid tools won't be in the map, so we return False.
|
||||
if (action.tool in nameToolMap) {
|
||||
if (nameToolMap[action.tool].returnDirect) {
|
||||
return {
|
||||
returnValues: { [returnValueKey]: observation },
|
||||
log: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
_returnStoppedResponse(earlyStoppingMethod: StoppingMethod) {
|
||||
if (earlyStoppingMethod === 'force') {
|
||||
return {
|
||||
returnValues: {
|
||||
output: 'Agent stopped due to iteration limit or time limit.'
|
||||
},
|
||||
log: ''
|
||||
} as AgentFinish
|
||||
}
|
||||
throw new Error(`Got unsupported early_stopping_method: ${earlyStoppingMethod}`)
|
||||
}
|
||||
|
||||
async *_streamIterator(inputs: Record<string, any>): AsyncGenerator<ChainValues> {
|
||||
const agentExecutorIterator = new AgentExecutorIterator({
|
||||
inputs,
|
||||
agentExecutor: this,
|
||||
metadata: this.metadata,
|
||||
tags: this.tags,
|
||||
callbacks: this.callbacks
|
||||
})
|
||||
const iterator = agentExecutorIterator.streamIterator()
|
||||
for await (const step of iterator) {
|
||||
if (!step) {
|
||||
continue
|
||||
}
|
||||
yield step
|
||||
}
|
||||
}
|
||||
|
||||
_chainType() {
|
||||
return 'agent_executor' as const
|
||||
}
|
||||
|
||||
serialize(): SerializedLLMChain {
|
||||
throw new Error('Cannot serialize an AgentExecutor')
|
||||
}
|
||||
}
|
||||
|
||||
class ExceptionTool extends Tool {
|
||||
name = '_Exception'
|
||||
|
||||
description = 'Exception tool'
|
||||
|
||||
async _call(query: string) {
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
export const formatAgentSteps = (steps: AgentStep[]): BaseMessage[] =>
|
||||
steps.flatMap(({ action, observation }) => {
|
||||
const create_function_message = (observation: string, action: AgentAction) => {
|
||||
let content: string
|
||||
if (typeof observation !== 'string') {
|
||||
content = JSON.stringify(observation)
|
||||
} else {
|
||||
content = observation
|
||||
}
|
||||
return new FunctionMessage(content, action.tool)
|
||||
}
|
||||
if ('messageLog' in action && action.messageLog !== undefined) {
|
||||
const log = action.messageLog as BaseMessage[]
|
||||
return log.concat(create_function_message(observation, action))
|
||||
} else {
|
||||
return [new AIMessage(action.log)]
|
||||
}
|
||||
})
|
||||
@@ -1,13 +1,17 @@
|
||||
import { BaseTracer, Run, BaseCallbackHandler } from 'langchain/callbacks'
|
||||
import { BaseTracer, Run, BaseCallbackHandler, LangChainTracer } from 'langchain/callbacks'
|
||||
import { AgentAction, ChainValues } from 'langchain/schema'
|
||||
import { Logger } from 'winston'
|
||||
import { Server } from 'socket.io'
|
||||
import { Client } from 'langsmith'
|
||||
import { LangChainTracer } from 'langchain/callbacks'
|
||||
import { LLMonitorHandler } from 'langchain/callbacks/handlers/llmonitor'
|
||||
import { LLMonitorHandler, LLMonitorHandlerFields } from 'langchain/callbacks/handlers/llmonitor'
|
||||
import { getCredentialData, getCredentialParam } from './utils'
|
||||
import { ICommonObject, INodeData } from './Interface'
|
||||
import CallbackHandler from 'langfuse-langchain'
|
||||
import { LangChainTracerFields } from '@langchain/core/tracers/tracer_langchain'
|
||||
import { RunTree, RunTreeConfig, Client as LangsmithClient } from 'langsmith'
|
||||
import { Langfuse, LangfuseTraceClient, LangfuseSpanClient, LangfuseGenerationClient } from 'langfuse'
|
||||
import monitor from 'llmonitor'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
interface AgentRun extends Run {
|
||||
actions: AgentAction[]
|
||||
@@ -231,11 +235,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
|
||||
apiKey: langSmithApiKey
|
||||
})
|
||||
|
||||
const tracer = new LangChainTracer({
|
||||
let langSmithField: LangChainTracerFields = {
|
||||
projectName: langSmithProject ?? 'default',
|
||||
//@ts-ignore
|
||||
client
|
||||
})
|
||||
}
|
||||
|
||||
if (nodeData?.inputs?.analytics?.langSmith) {
|
||||
langSmithField = { ...langSmithField, ...nodeData?.inputs?.analytics?.langSmith }
|
||||
}
|
||||
|
||||
const tracer = new LangChainTracer(langSmithField)
|
||||
callbacks.push(tracer)
|
||||
} else if (provider === 'langFuse') {
|
||||
const release = analytic[provider].release as string
|
||||
@@ -244,13 +254,17 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
|
||||
const langFusePublicKey = getCredentialParam('langFusePublicKey', credentialData, nodeData)
|
||||
const langFuseEndpoint = getCredentialParam('langFuseEndpoint', credentialData, nodeData)
|
||||
|
||||
const langFuseOptions: any = {
|
||||
let langFuseOptions: any = {
|
||||
secretKey: langFuseSecretKey,
|
||||
publicKey: langFusePublicKey,
|
||||
baseUrl: langFuseEndpoint ?? 'https://cloud.langfuse.com'
|
||||
}
|
||||
if (release) langFuseOptions.release = release
|
||||
if (options.chatId) langFuseOptions.userId = options.chatId
|
||||
if (options.chatId) langFuseOptions.sessionId = options.chatId
|
||||
|
||||
if (nodeData?.inputs?.analytics?.langFuse) {
|
||||
langFuseOptions = { ...langFuseOptions, ...nodeData?.inputs?.analytics?.langFuse }
|
||||
}
|
||||
|
||||
const handler = new CallbackHandler(langFuseOptions)
|
||||
callbacks.push(handler)
|
||||
@@ -258,11 +272,15 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
|
||||
const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, nodeData)
|
||||
const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, nodeData)
|
||||
|
||||
const llmonitorFields: ICommonObject = {
|
||||
let llmonitorFields: LLMonitorHandlerFields = {
|
||||
appId: llmonitorAppId,
|
||||
apiUrl: llmonitorEndpoint ?? 'https://app.llmonitor.com'
|
||||
}
|
||||
|
||||
if (nodeData?.inputs?.analytics?.llmonitor) {
|
||||
llmonitorFields = { ...llmonitorFields, ...nodeData?.inputs?.analytics?.llmonitor }
|
||||
}
|
||||
|
||||
const handler = new LLMonitorHandler(llmonitorFields)
|
||||
callbacks.push(handler)
|
||||
}
|
||||
@@ -273,3 +291,491 @@ export const additionalCallbacks = async (nodeData: INodeData, options: ICommonO
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
export class AnalyticHandler {
|
||||
nodeData: INodeData
|
||||
options: ICommonObject = {}
|
||||
handlers: ICommonObject = {}
|
||||
|
||||
constructor(nodeData: INodeData, options: ICommonObject) {
|
||||
this.options = options
|
||||
this.nodeData = nodeData
|
||||
this.init()
|
||||
}
|
||||
|
||||
async init() {
|
||||
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',
|
||||
release
|
||||
})
|
||||
this.handlers['langFuse'] = { client: langfuse }
|
||||
} else if (provider === 'llmonitor') {
|
||||
const llmonitorAppId = getCredentialParam('llmonitorAppId', credentialData, this.nodeData)
|
||||
const llmonitorEndpoint = getCredentialParam('llmonitorEndpoint', credentialData, this.nodeData)
|
||||
|
||||
monitor.init({
|
||||
appId: llmonitorAppId,
|
||||
apiUrl: llmonitorEndpoint
|
||||
})
|
||||
|
||||
this.handlers['llmonitor'] = { client: monitor }
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
async onChainStart(name: string, input: string, parentIds?: ICommonObject) {
|
||||
const returnIds: ICommonObject = {
|
||||
langSmith: {},
|
||||
langFuse: {},
|
||||
llmonitor: {}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
if (!parentIds || !Object.keys(parentIds).length) {
|
||||
const parentRunConfig: RunTreeConfig = {
|
||||
name,
|
||||
run_type: 'chain',
|
||||
inputs: {
|
||||
text: input
|
||||
},
|
||||
serialized: {},
|
||||
project_name: this.handlers['langSmith'].langSmithProject,
|
||||
client: this.handlers['langSmith'].client,
|
||||
...this.nodeData?.inputs?.analytics?.langSmith
|
||||
}
|
||||
const parentRun = new RunTree(parentRunConfig)
|
||||
await parentRun.postRun()
|
||||
this.handlers['langSmith'].chainRun = { [parentRun.id]: parentRun }
|
||||
returnIds['langSmith'].chainRun = parentRun.id
|
||||
} else {
|
||||
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||
if (parentRun) {
|
||||
const childChainRun = await parentRun.createChild({
|
||||
name,
|
||||
run_type: 'chain',
|
||||
inputs: {
|
||||
text: input
|
||||
}
|
||||
})
|
||||
await childChainRun.postRun()
|
||||
this.handlers['langSmith'].chainRun = { [childChainRun.id]: childChainRun }
|
||||
returnIds['langSmith'].chainRun = childChainRun.id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
let langfuseTraceClient: LangfuseTraceClient
|
||||
|
||||
if (!parentIds || !Object.keys(parentIds).length) {
|
||||
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||
langfuseTraceClient = langfuse.trace({
|
||||
name,
|
||||
sessionId: this.options.chatId,
|
||||
metadata: { tags: ['openai-assistant'] },
|
||||
...this.nodeData?.inputs?.analytics?.langFuse
|
||||
})
|
||||
} else {
|
||||
langfuseTraceClient = this.handlers['langFuse'].trace[parentIds['langFuse']]
|
||||
}
|
||||
|
||||
if (langfuseTraceClient) {
|
||||
const span = langfuseTraceClient.span({
|
||||
name,
|
||||
input: {
|
||||
text: input
|
||||
}
|
||||
})
|
||||
this.handlers['langFuse'].trace = { [langfuseTraceClient.id]: langfuseTraceClient }
|
||||
this.handlers['langFuse'].span = { [span.id]: span }
|
||||
returnIds['langFuse'].trace = langfuseTraceClient.id
|
||||
returnIds['langFuse'].span = span.id
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor) {
|
||||
const runId = uuidv4()
|
||||
await monitor.trackEvent('chain', 'start', {
|
||||
runId,
|
||||
name,
|
||||
userId: this.options.chatId,
|
||||
input,
|
||||
...this.nodeData?.inputs?.analytics?.llmonitor
|
||||
})
|
||||
this.handlers['llmonitor'].chainEvent = { [runId]: runId }
|
||||
returnIds['llmonitor'].chainEvent = runId
|
||||
}
|
||||
}
|
||||
|
||||
return returnIds
|
||||
}
|
||||
|
||||
async onChainEnd(returnIds: ICommonObject, output: string | object, shutdown = false) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const chainRun: RunTree | undefined = this.handlers['langSmith'].chainRun[returnIds['langSmith'].chainRun]
|
||||
if (chainRun) {
|
||||
await chainRun.end({
|
||||
outputs: {
|
||||
output
|
||||
}
|
||||
})
|
||||
await chainRun.patchRun()
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const span: LangfuseSpanClient | undefined = this.handlers['langFuse'].span[returnIds['langFuse'].span]
|
||||
if (span) {
|
||||
span.end({
|
||||
output
|
||||
})
|
||||
if (shutdown) {
|
||||
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||
await langfuse.shutdownAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const chainEventId = returnIds['llmonitor'].chainEvent
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor && chainEventId) {
|
||||
await monitor.trackEvent('chain', 'end', {
|
||||
runId: chainEventId,
|
||||
output
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onChainError(returnIds: ICommonObject, error: string | object, shutdown = false) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const chainRun: RunTree | undefined = this.handlers['langSmith'].chainRun[returnIds['langSmith'].chainRun]
|
||||
if (chainRun) {
|
||||
await chainRun.end({
|
||||
error: {
|
||||
error
|
||||
}
|
||||
})
|
||||
await chainRun.patchRun()
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const span: LangfuseSpanClient | undefined = this.handlers['langFuse'].span[returnIds['langFuse'].span]
|
||||
if (span) {
|
||||
span.end({
|
||||
output: {
|
||||
error
|
||||
}
|
||||
})
|
||||
if (shutdown) {
|
||||
const langfuse: Langfuse = this.handlers['langFuse'].client
|
||||
await langfuse.shutdownAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const chainEventId = returnIds['llmonitor'].chainEvent
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor && chainEventId) {
|
||||
await monitor.trackEvent('chain', 'end', {
|
||||
runId: chainEventId,
|
||||
output: error
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onLLMStart(name: string, input: string, parentIds: ICommonObject) {
|
||||
const returnIds: ICommonObject = {
|
||||
langSmith: {},
|
||||
langFuse: {},
|
||||
llmonitor: {}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||
if (parentRun) {
|
||||
const childLLMRun = await parentRun.createChild({
|
||||
name,
|
||||
run_type: 'llm',
|
||||
inputs: {
|
||||
prompts: [input]
|
||||
}
|
||||
})
|
||||
await childLLMRun.postRun()
|
||||
this.handlers['langSmith'].llmRun = { [childLLMRun.id]: childLLMRun }
|
||||
returnIds['langSmith'].llmRun = childLLMRun.id
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace]
|
||||
if (trace) {
|
||||
const generation = trace.generation({
|
||||
name,
|
||||
input: input
|
||||
})
|
||||
this.handlers['langFuse'].generation = { [generation.id]: generation }
|
||||
returnIds['langFuse'].generation = generation.id
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
const chainEventId: string = this.handlers['llmonitor'].chainEvent[parentIds['llmonitor'].chainEvent]
|
||||
|
||||
if (monitor && chainEventId) {
|
||||
const runId = uuidv4()
|
||||
await monitor.trackEvent('llm', 'start', {
|
||||
runId,
|
||||
parentRunId: chainEventId,
|
||||
name,
|
||||
userId: this.options.chatId,
|
||||
input
|
||||
})
|
||||
this.handlers['llmonitor'].llmEvent = { [runId]: runId }
|
||||
returnIds['llmonitor'].llmEvent = runId
|
||||
}
|
||||
}
|
||||
|
||||
return returnIds
|
||||
}
|
||||
|
||||
async onLLMEnd(returnIds: ICommonObject, output: string) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const llmRun: RunTree | undefined = this.handlers['langSmith'].llmRun[returnIds['langSmith'].llmRun]
|
||||
if (llmRun) {
|
||||
await llmRun.end({
|
||||
outputs: {
|
||||
generations: [output]
|
||||
}
|
||||
})
|
||||
await llmRun.patchRun()
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation]
|
||||
if (generation) {
|
||||
generation.end({
|
||||
output: output
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const llmEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].llmEvent]
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor && llmEventId) {
|
||||
await monitor.trackEvent('llm', 'end', {
|
||||
runId: llmEventId,
|
||||
output
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onLLMError(returnIds: ICommonObject, error: string | object) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const llmRun: RunTree | undefined = this.handlers['langSmith'].llmRun[returnIds['langSmith'].llmRun]
|
||||
if (llmRun) {
|
||||
await llmRun.end({
|
||||
error: {
|
||||
error
|
||||
}
|
||||
})
|
||||
await llmRun.patchRun()
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const generation: LangfuseGenerationClient | undefined = this.handlers['langFuse'].generation[returnIds['langFuse'].generation]
|
||||
if (generation) {
|
||||
generation.end({
|
||||
output: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const llmEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].llmEvent]
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor && llmEventId) {
|
||||
await monitor.trackEvent('llm', 'end', {
|
||||
runId: llmEventId,
|
||||
output: error
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onToolStart(name: string, input: string | object, parentIds: ICommonObject) {
|
||||
const returnIds: ICommonObject = {
|
||||
langSmith: {},
|
||||
langFuse: {},
|
||||
llmonitor: {}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const parentRun: RunTree | undefined = this.handlers['langSmith'].chainRun[parentIds['langSmith'].chainRun]
|
||||
if (parentRun) {
|
||||
const childToolRun = await parentRun.createChild({
|
||||
name,
|
||||
run_type: 'tool',
|
||||
inputs: {
|
||||
input
|
||||
}
|
||||
})
|
||||
await childToolRun.postRun()
|
||||
this.handlers['langSmith'].toolRun = { [childToolRun.id]: childToolRun }
|
||||
returnIds['langSmith'].toolRun = childToolRun.id
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const trace: LangfuseTraceClient | undefined = this.handlers['langFuse'].trace[parentIds['langFuse'].trace]
|
||||
if (trace) {
|
||||
const toolSpan = trace.span({
|
||||
name,
|
||||
input
|
||||
})
|
||||
this.handlers['langFuse'].toolSpan = { [toolSpan.id]: toolSpan }
|
||||
returnIds['langFuse'].toolSpan = toolSpan.id
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
const chainEventId: string = this.handlers['llmonitor'].chainEvent[parentIds['llmonitor'].chainEvent]
|
||||
|
||||
if (monitor && chainEventId) {
|
||||
const runId = uuidv4()
|
||||
await monitor.trackEvent('tool', 'start', {
|
||||
runId,
|
||||
parentRunId: chainEventId,
|
||||
name,
|
||||
userId: this.options.chatId,
|
||||
input
|
||||
})
|
||||
this.handlers['llmonitor'].toolEvent = { [runId]: runId }
|
||||
returnIds['llmonitor'].toolEvent = runId
|
||||
}
|
||||
}
|
||||
|
||||
return returnIds
|
||||
}
|
||||
|
||||
async onToolEnd(returnIds: ICommonObject, output: string | object) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const toolRun: RunTree | undefined = this.handlers['langSmith'].toolRun[returnIds['langSmith'].toolRun]
|
||||
if (toolRun) {
|
||||
await toolRun.end({
|
||||
outputs: {
|
||||
output
|
||||
}
|
||||
})
|
||||
await toolRun.patchRun()
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const toolSpan: LangfuseSpanClient | undefined = this.handlers['langFuse'].toolSpan[returnIds['langFuse'].toolSpan]
|
||||
if (toolSpan) {
|
||||
toolSpan.end({
|
||||
output
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const toolEventId: string = this.handlers['llmonitor'].toolEvent[returnIds['llmonitor'].toolEvent]
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor && toolEventId) {
|
||||
await monitor.trackEvent('tool', 'end', {
|
||||
runId: toolEventId,
|
||||
output
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onToolError(returnIds: ICommonObject, error: string | object) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langSmith')) {
|
||||
const toolRun: RunTree | undefined = this.handlers['langSmith'].toolRun[returnIds['langSmith'].toolRun]
|
||||
if (toolRun) {
|
||||
await toolRun.end({
|
||||
error: {
|
||||
error
|
||||
}
|
||||
})
|
||||
await toolRun.patchRun()
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'langFuse')) {
|
||||
const toolSpan: LangfuseSpanClient | undefined = this.handlers['langFuse'].toolSpan[returnIds['langFuse'].toolSpan]
|
||||
if (toolSpan) {
|
||||
toolSpan.end({
|
||||
output: error
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.handlers, 'llmonitor')) {
|
||||
const toolEventId: string = this.handlers['llmonitor'].llmEvent[returnIds['llmonitor'].toolEvent]
|
||||
const monitor = this.handlers['llmonitor'].client
|
||||
|
||||
if (monitor && toolEventId) {
|
||||
await monitor.trackEvent('tool', 'end', {
|
||||
runId: toolEventId,
|
||||
output: error
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,86 @@ import * as path from 'path'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import { z } from 'zod'
|
||||
import { DataSource } from 'typeorm'
|
||||
import { ICommonObject, IDatabaseEntity, IMessage, INodeData } from './Interface'
|
||||
import { ICommonObject, IDatabaseEntity, IMessage, INodeData, IVariable } from './Interface'
|
||||
import { AES, enc } from 'crypto-js'
|
||||
import { ChatMessageHistory } from 'langchain/memory'
|
||||
import { AIMessage, HumanMessage, BaseMessage } from 'langchain/schema'
|
||||
|
||||
export const numberOrExpressionRegex = '^(\\d+\\.?\\d*|{{.*}})$' //return true if string consists only numbers OR expression {{}}
|
||||
export const notEmptyRegex = '(.|\\s)*\\S(.|\\s)*' //return true if string is not empty or blank
|
||||
/*
|
||||
* List of dependencies allowed to be import in vm2
|
||||
*/
|
||||
export const availableDependencies = [
|
||||
'@aws-sdk/client-bedrock-runtime',
|
||||
'@aws-sdk/client-dynamodb',
|
||||
'@aws-sdk/client-s3',
|
||||
'@elastic/elasticsearch',
|
||||
'@dqbd/tiktoken',
|
||||
'@getzep/zep-js',
|
||||
'@gomomento/sdk',
|
||||
'@gomomento/sdk-core',
|
||||
'@google-ai/generativelanguage',
|
||||
'@huggingface/inference',
|
||||
'@notionhq/client',
|
||||
'@opensearch-project/opensearch',
|
||||
'@pinecone-database/pinecone',
|
||||
'@qdrant/js-client-rest',
|
||||
'@supabase/supabase-js',
|
||||
'@upstash/redis',
|
||||
'@zilliz/milvus2-sdk-node',
|
||||
'apify-client',
|
||||
'axios',
|
||||
'cheerio',
|
||||
'chromadb',
|
||||
'cohere-ai',
|
||||
'd3-dsv',
|
||||
'faiss-node',
|
||||
'form-data',
|
||||
'google-auth-library',
|
||||
'graphql',
|
||||
'html-to-text',
|
||||
'ioredis',
|
||||
'langchain',
|
||||
'langfuse',
|
||||
'langsmith',
|
||||
'linkifyjs',
|
||||
'llmonitor',
|
||||
'mammoth',
|
||||
'moment',
|
||||
'mongodb',
|
||||
'mysql2',
|
||||
'node-fetch',
|
||||
'node-html-markdown',
|
||||
'notion-to-md',
|
||||
'openai',
|
||||
'pdf-parse',
|
||||
'pdfjs-dist',
|
||||
'pg',
|
||||
'playwright',
|
||||
'puppeteer',
|
||||
'redis',
|
||||
'replicate',
|
||||
'srt-parser-2',
|
||||
'typeorm',
|
||||
'weaviate-ts-client'
|
||||
]
|
||||
|
||||
export const defaultAllowBuiltInDep = [
|
||||
'assert',
|
||||
'buffer',
|
||||
'crypto',
|
||||
'events',
|
||||
'http',
|
||||
'https',
|
||||
'net',
|
||||
'path',
|
||||
'querystring',
|
||||
'timers',
|
||||
'tls',
|
||||
'url',
|
||||
'zlib'
|
||||
]
|
||||
|
||||
/**
|
||||
* Get base classes of components
|
||||
@@ -379,7 +452,8 @@ const getEncryptionKeyFilePath = (): string => {
|
||||
path.join(__dirname, '..', '..', '..', '..', 'encryption.key'),
|
||||
path.join(__dirname, '..', '..', '..', '..', 'server', 'encryption.key'),
|
||||
path.join(__dirname, '..', '..', '..', '..', '..', 'encryption.key'),
|
||||
path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key')
|
||||
path.join(__dirname, '..', '..', '..', '..', '..', 'server', 'encryption.key'),
|
||||
path.join(getUserHome(), '.flowise', 'encryption.key')
|
||||
]
|
||||
for (const checkPath of checkPaths) {
|
||||
if (fs.existsSync(checkPath)) {
|
||||
@@ -389,7 +463,7 @@ const getEncryptionKeyFilePath = (): string => {
|
||||
return ''
|
||||
}
|
||||
|
||||
const getEncryptionKeyPath = (): string => {
|
||||
export const getEncryptionKeyPath = (): string => {
|
||||
return process.env.SECRETKEY_PATH ? path.join(process.env.SECRETKEY_PATH, 'encryption.key') : getEncryptionKeyFilePath()
|
||||
}
|
||||
|
||||
@@ -612,9 +686,8 @@ export const flattenObject = (obj: ICommonObject, parentKey?: string) => {
|
||||
|
||||
/**
|
||||
* Convert BaseMessage to IMessage
|
||||
* @param {ICommonObject} obj
|
||||
* @param {string} parentKey
|
||||
* @returns {ICommonObject}
|
||||
* @param {BaseMessage[]} messages
|
||||
* @returns {IMessage[]}
|
||||
*/
|
||||
export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[] => {
|
||||
const formatmessages: IMessage[] = []
|
||||
@@ -638,3 +711,72 @@ export const convertBaseMessagetoIMessage = (messages: BaseMessage[]): IMessage[
|
||||
}
|
||||
return formatmessages
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert MultiOptions String to String Array
|
||||
* @param {string} inputString
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export const convertMultiOptionsToStringArray = (inputString: string): string[] => {
|
||||
let ArrayString: string[] = []
|
||||
try {
|
||||
ArrayString = JSON.parse(inputString)
|
||||
} catch (e) {
|
||||
ArrayString = []
|
||||
}
|
||||
return ArrayString
|
||||
}
|
||||
|
||||
/**
|
||||
* Get variables
|
||||
* @param {DataSource} appDataSource
|
||||
* @param {IDatabaseEntity} databaseEntities
|
||||
* @param {INodeData} nodeData
|
||||
*/
|
||||
export const getVars = async (appDataSource: DataSource, databaseEntities: IDatabaseEntity, nodeData: INodeData) => {
|
||||
const variables = ((await appDataSource.getRepository(databaseEntities['Variable']).find()) as IVariable[]) ?? []
|
||||
|
||||
// override variables defined in overrideConfig
|
||||
// nodeData.inputs.variables is an Object, check each property and override the variable
|
||||
if (nodeData?.inputs?.vars) {
|
||||
for (const propertyName of Object.getOwnPropertyNames(nodeData.inputs.vars)) {
|
||||
const foundVar = variables.find((v) => v.name === propertyName)
|
||||
if (foundVar) {
|
||||
// even if the variable was defined as runtime, we override it with static value
|
||||
foundVar.type = 'static'
|
||||
foundVar.value = nodeData.inputs.vars[propertyName]
|
||||
} else {
|
||||
// add it the variables, if not found locally in the db
|
||||
variables.push({ name: propertyName, type: 'static', value: nodeData.inputs.vars[propertyName] })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return variables
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare sandbox variables
|
||||
* @param {IVariable[]} variables
|
||||
*/
|
||||
export const prepareSandboxVars = (variables: IVariable[]) => {
|
||||
let vars = {}
|
||||
if (variables) {
|
||||
for (const item of variables) {
|
||||
let value = item.value
|
||||
|
||||
// read from .env file
|
||||
if (item.type === 'runtime') {
|
||||
value = process.env[item.name] ?? ''
|
||||
}
|
||||
|
||||
Object.defineProperty(vars, item.name, {
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: value
|
||||
})
|
||||
}
|
||||
}
|
||||
return vars
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user