UNPKG

@convo-lang/convo-lang

Version:
681 lines (677 loc) 26.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConvoGraphCtrl = void 0; const common_1 = require("@iyio/common"); const rxjs_1 = require("rxjs"); const Conversation_1 = require("./Conversation"); const convo_graph_lib_1 = require("./convo-graph-lib"); const convo_lib_1 = require("./convo-lib"); const convo_template_1 = require("./convo-template"); const convo_deps_1 = require("./convo.deps"); class ConvoGraphCtrl { store; _tokenUsage = new rxjs_1.BehaviorSubject((0, convo_lib_1.createEmptyConvoTokenUsage)()); get tokenUsageSubject() { return this._tokenUsage; } get tokenUsage() { return this._tokenUsage.value; } _onMonitorEvent = new rxjs_1.Subject(); get onMonitorEvent() { return this._onMonitorEvent; } get hasListeners() { return this._onMonitorEvent.observed || this.logEventsToConsole; } triggerEvent(evt) { evt.time = Date.now(); this._onMonitorEvent.next(evt); if (this.logEventsToConsole) { console.info((0, convo_graph_lib_1.getConvoGraphEventString)(evt, this._tokenUsage.value)); } } logEventsToConsole; beforeNext; defaultConvoOptions; async getConvoOptionsAsync(tv, initConvo, defaultVarsOverride) { return { ...this.defaultConvoOptions, disableAutoFlatten: true, onTokenUsage: usage => this.addTokenUsage(usage), initConvo: ((await this.getSharedSourceAsync()) + ((initConvo ? (this.defaultConvoOptions.initConvo ? `${this.defaultConvoOptions.initConvo}\n\n${initConvo}` : initConvo) : this.defaultConvoOptions.initConvo) || '')) || undefined, defaultVars: { ...this.defaultConvoOptions.defaultVars, input: tv?.payload, sourceInput: tv?.payload, workflow: tv?.state, graphCtrl: this, traverser: tv, ...defaultVarsOverride } }; } addTokenUsage(usage) { if ((0, convo_lib_1.isConvoTokenUsageEmpty)(usage)) { return; } const u = { ...this._tokenUsage.value }; (0, convo_lib_1.addConvoUsageTokens)(u, usage); this._tokenUsage.next(u); } maxConcurrentStepExe; stepLock; constructor({ store = (0, convo_deps_1.convoGraphStore)(), convoOptions = {}, maxConcurrentStepExe = convo_graph_lib_1.maxConvoGraphConcurrentStepExe, logEventsToConsole = false, beforeNext, }) { this.store = store; this.defaultConvoOptions = convoOptions; this.maxConcurrentStepExe = Math.max(1, maxConcurrentStepExe); this.stepLock = new common_1.Lock(this.maxConcurrentStepExe); this.logEventsToConsole = logEventsToConsole; this.beforeNext = beforeNext; } disposables = new common_1.DisposeContainer(); _isDisposed = false; get isDisposed() { return this._isDisposed; } dispose() { if (this._isDisposed) { return; } this.disposables.dispose(); this._isDisposed = true; } async startRunAsync(options) { const tv = await this.startTraversalAsync(options); await this.runGroupAsync(tv); return tv; } async startTraversalAsync({ createTvOptions, edge, edgePattern, payload = {}, state, saveToStore = false, cancel = new common_1.CancelToken(), traversers: traversersOpt, }) { if (typeof edge === 'string') { edge = { id: (0, common_1.shortUuid)(), to: edge, from: '' }; } const traversers = []; if (traversersOpt) { traversers.push(...traversersOpt); } else { let edges; if (edge) { edges = [edge]; } else if (edgePattern) { edges = await this.getEdgesAsync(edgePattern); } else { edges = []; } const traversersArrays = await Promise.all(edges.map((edge) => this.createTvAsync(edge, createTvOptions, payload, state, saveToStore))); for (const ta of traversersArrays) { traversers.push(...ta); } } const first = traversers.find(t => t.controlPath); if (first) { (0, convo_graph_lib_1.applyConvoTraverserControlPath)(first); } return { traversers: new rxjs_1.BehaviorSubject(traversers), saveToStore, createTvOptions, cancel }; } async createTvAsync(edge, options, payload, state, saveToStore, addTo) { if (edge.selectPath) { payload = (0, common_1.getValueByPath)(payload, edge.selectPath); } if (edge.loop && Array.isArray(payload)) { return await Promise.all(payload.map(p => this._createTvAsync(edge, options, edge.loopSelectPath ? (0, common_1.getValueByPath)(p, edge.loopSelectPath) : p, state, saveToStore, addTo))); } else { return [await this._createTvAsync(edge, options, payload, state, saveToStore, addTo)]; } } async _createTvAsync(edge, options, payload, state, saveToStore, addTo) { let defaults = options?.defaults; if (typeof defaults === 'function') { defaults = defaults(edge, options, payload, state, saveToStore); } const tv = { ...defaults, id: defaults?.id ?? (0, common_1.shortUuid)(), exeState: 'invoked', state: defaults?.state ?? {}, currentStepIndex: 0, saveToStore }; tv.payload = payload; if (state) { for (const e in state) { tv.state[e] = state[e]; } } await options?.initTraverser?.(tv); if (this.hasListeners) { this.triggerEvent({ type: 'start-traversal', text: 'Graph traversal started', traverser: tv }); } await this.traverseEdgeAsync(tv, edge); if (addTo) { (0, common_1.pushBehaviorSubjectAry)(addTo, tv); } return tv; } /** * Moves the traverser to the "to" side of the edge and updates the traversers execution state. * If the target node of the edge can not be found the traverser's execution state will be * set to failed. */ async traverseEdgeAsync(tv, edge) { if (tv.exeState !== 'invoked') { throw new Error('ConvoTraverser execution state must be set to `invoked` before traversing an edge'); } edge = (0, common_1.deepClone)(edge); const targetNode = await this.store.getNodeAsync(edge.to); if (!targetNode) { tv.exeState = 'failed'; tv.errorMessage = `Target to node with id ${edge.to} does not exist`; if (this.hasListeners) { this.triggerEvent({ type: 'traversal-failed', text: tv.errorMessage, traverser: tv, edge }); } if (tv.saveToStore) { await this.saveTraverserAsync(tv); } return undefined; } tv.currentNodeId = targetNode.id; if (!tv.startingNodeId) { tv.startingNodeId = targetNode.id; } if (edge.pause) { tv.exeState = 'paused'; tv.pause = edge.pause; if (tv.pause.delayMs !== undefined) { tv.resumeAt = Date.now() + tv.pause.delayMs; } else { delete tv.resumeAt; } } else { tv.exeState = 'ready'; delete tv.resumeAt; delete tv.pause; } if (!tv.path) { tv.path = []; } tv.path.push(edge.id); if (this.hasListeners) { this.triggerEvent({ type: 'edge-crossed', text: 'Traverser crossed edge', pause: tv.pause ? { ...tv.pause } : undefined, traverser: tv, edge, node: targetNode }); } if (tv.saveToStore) { await this.saveTraverserAsync(tv); } return targetNode; } async saveTraverserAsync(tv) { (0, convo_graph_lib_1.applyConvoTraverserControlPath)(tv); const putP = this.store.putTraverserAsync(tv); if (this.store.putTraverserProxiesAsync) { await Promise.all([putP, this.store.putTraverserProxiesAsync(tv)]); } else { await putP; } } async runGroupAsync(group) { const running = []; const runPromises = []; const startSrc = (0, common_1.createPromiseSource)(); const sub = group.traversers.subscribe(ary => { for (const t of ary) { if (!running.includes(t)) { running.push(t); const runP = this.runAsync(t, group); runPromises.push(runP); runP.then(() => { (0, common_1.aryRemoveItem)(runPromises, runP); }); } } startSrc.resolve(); }); await startSrc.promise; while (runPromises.length) { await Promise.all(runPromises); } sub.unsubscribe(); } async runAsync(tv, group) { await this.store.loadTraverserProxiesAsync?.(tv); while ((!this.beforeNext || await this.beforeNext(tv, group, this)) && (await this.nextAsync(tv, group)) === 'ready' && !group?.cancel.isCanceled) { // do nothing } return tv.exeState; } /** * Executes the current node the traverser in on then traverses to the next node or stops if * no matching edges are found. */ async nextAsync(tv, group) { if (tv.exeState !== 'ready') { throw new Error('ConvoTraverser execution state must be set to `ready` before executing a node'); } if (!tv.currentNodeId) { throw new Error('ConvoTraverser does not have its currentNodeId set'); } try { const newState = await this._nextAsync(tv, group); if (newState === 'failed') { if (this.hasListeners) { this.triggerEvent({ type: 'traversal-failed', text: tv.errorMessage ?? 'Traversal failed', traverser: tv, }); } } return newState; } catch (ex) { tv.currentStepIndex = 0; //throw errors can be retired if (this.hasListeners) { this.triggerEvent({ type: 'traversal-failed', text: (0, common_1.getErrorMessage)(ex), traverser: tv, }); } return 'failed'; } } async getNodeMetadataAsync(node) { const convoOptions = await this.getConvoOptionsAsync(undefined); const defaultVars = { ...convoOptions?.defaultVars }; return await (0, convo_graph_lib_1.getConvoNodeMetadataAsync)((node.sharedConvo || node.steps.length) ? (0, convo_graph_lib_1.createConvoNodeExecCtxConvo)(node, defaultVars, convoOptions, (node.steps[node.steps.length - 1]?.convo ?? '')) : null); } async _nextAsync(tv, group) { const node = await this.store.getNodeAsync(tv.currentNodeId ?? ''); if (!node) { tv.exeState = 'failed'; tv.errorMessage = `Target node with id ${tv.currentNodeId} not found while trying to execute`; return tv.exeState; } if (this.hasListeners) { this.triggerEvent({ type: 'start-exe', text: 'Starting execution of node', traverser: tv, node, }); } const startSuffix = tv.state[convo_graph_lib_1.convoTraverserStateStoreSuffix]; const startProxyPaths = {}; let map = tv.state[convo_graph_lib_1.convoTraverserProxyVar]; if (this.store.loadTraverserProxiesAsync && map && (typeof map === 'object')) { for (const e in map) { const p = map[e]; if (!p) { continue; } if (typeof p === 'string') { startProxyPaths[e] = p; } else { startProxyPaths[e] = p.path; } } } const checkProxyAsync = async (suffixChanged) => { map = tv.state[convo_graph_lib_1.convoTraverserProxyVar]; if (this.store.loadTraverserProxiesAsync && map && (typeof map === 'object')) { const changes = []; for (const e in map) { const p = map[e]; if (!p) { continue; } let path; if (typeof p === 'string') { path = p; } else { path = p.path; } if (path !== startProxyPaths[e]) { changes.push(e); } } if (changes.length || suffixChanged) { await this.store.loadTraverserProxiesAsync(tv, changes); } } }; const exeCtx = await (0, convo_graph_lib_1.createConvoNodeExecCtxAsync)(node, await this.getConvoOptionsAsync(tv)); await checkProxyAsync(false); // transform input let transformStep = null; if (exeCtx.metadata.inputType?.name) { const inputType = exeCtx.typeMap[exeCtx.metadata.inputType.name]; if (inputType) { transformStep = await this.transformInputAsync(tv, node, inputType, exeCtx); } else { tv.exeState = 'failed'; tv.errorMessage = 'Input type not found for transforming'; } if (tv.exeState === 'failed') { return 'failed'; } } let invokeCall; tv.exeState = 'invoking'; for (let i = transformStep ? -1 : 0; i < exeCtx.steps.length; i++) { const step = i === -1 ? transformStep : exeCtx.steps[i]; if (!step) { continue; } tv.currentStepIndex = i; invokeCall = await this.executeStepAsync(tv, node, step, i, exeCtx); if (tv.exeState === 'failed') { tv.currentStepIndex = 0; return 'failed'; } await checkProxyAsync(false); } tv.currentStepIndex = 0; tv.exeState = 'invoked'; const newSuffix = tv.state[convo_graph_lib_1.convoTraverserStateStoreSuffix]; const suffixChanged = (newSuffix && startSuffix !== newSuffix) ? true : false; if (suffixChanged) { const stateTv = await this.store.getTraverserAsync(tv.id, newSuffix); if (stateTv) { for (const e in stateTv.state) { if (e === convo_graph_lib_1.convoTraverserStateStoreSuffix) { continue; } tv.state[e] = stateTv.state[e]; } } } await checkProxyAsync(suffixChanged); await exeCtx.convo.flattenAsync(); const edges = await this.getEdgesAsync({ from: node.id, fromFn: invokeCall?.call?.fn.name, fromType: invokeCall?.type, input: tv.payload, workflow: tv?.state, }); if (edges.length) { await Promise.all(edges.map(async (edge, i) => { if (i === 0 && !edge.loop) { if (edge.loop) { tv.exeState = 'stopped'; this.triggerEvent({ type: 'traversal-stopped', text: 'Traversal yeilded to loop', traverser: tv, node, }); if (tv.saveToStore) { await this.saveTraverserAsync(tv); } } else { if (edge.selectPath) { tv.payload = (0, common_1.getValueByPath)(tv.payload, edge.selectPath); } await this.traverseEdgeAsync(tv, edge); return; } } // create fork await this.createTvAsync(edge, group?.createTvOptions, tv.payload, tv.state, group?.saveToStore ?? false, group?.traversers); })); } else { tv.exeState = 'stopped'; this.triggerEvent({ type: 'traversal-stopped', text: 'Traversal stopped', traverser: tv, node, }); if (tv.saveToStore) { await this.saveTraverserAsync(tv); } } return tv.exeState; } /** * Returns all edges that match the given pattern */ async getEdgesAsync({ from, fromType, fromFn, input, workflow, }) { let edges = await this.store.getNodeEdgesAsync(from, 'from'); if (fromType || fromFn) { edges = edges.filter(e => (((fromType && e.fromType) ? e.fromType === fromType : true) && ((fromFn && e.fromFn) ? e.fromFn === fromFn : true))); } for (let i = 0; i < edges.length; i++) { const edge = edges[i]; if (!edge?.conditionConvo) { continue; } try { const conversation = new Conversation_1.Conversation(await this.getConvoOptionsAsync(undefined, `\n> edgeConditionEvalFunction() -> ( ${edge.conditionConvo} )\n` + `> do` + `edgeConditionResult=edgeConditionEvalFunction()`, { input, workflow })); if (edge.userData) { const varName = edge.userDataVarName === undefined ? convo_graph_lib_1.defaultConvoGraphUserDataVarName : edge.userDataVarName; if (varName && conversation.defaultVars[varName] === undefined) { conversation.defaultVars[varName] = { ...edge.userData }; } } const flat = await conversation.flattenAsync(); const accept = flat.exe.getVar('edgeConditionResult'); if (!accept) { edges.splice(i, 1); i--; } } catch (ex) { console.error('Edge condition error', edge, ex); edges.splice(i, 1); i--; } } return edges; } async getSharedSourceAsync() { const nodes = (await this.store.getSourceNodesAsync()).filter(s => s.shared); if (!nodes.length) { return ''; } return nodes.map(n => n.source ?? '').join('\n\n') + '\n\n'; } async transformInputAsync(tv, node, inputType, exeCtx) { const parsed = inputType.safeParse(tv.payload); if (parsed.success) { tv.payload = parsed.data; } else { const co = (0, common_1.zodCoerceObject)(inputType, (((typeof tv.payload === 'object') && tv.payload) ? tv.payload : { value: tv.payload })); if (co.error) { if (!node.disableAutoTransform) { const transformStep = { name: 'Auto transform', resetConvo: true, convo: (0, convo_template_1.convoScript) ` @output # Sets the current input to the newly transformed input > setConverted() Input -> ( return(__args) ) @errorCallback # Call this function if you are unable to convert the arguments > conversionFailed( errorMessage?:string ) -> ( return(or(errorMessage 'failed')) ) > user Call the setConverted function using the sourceInput below. Convert the sourceInput as needed to match the parameters of setConverted. sourceInput: {{input}} ` }; if (this.hasListeners) { this.triggerEvent({ type: 'auto-transformer-created', text: `Auto transformer created`, traverser: tv, node, step: transformStep }); } return { nodeStep: transformStep, // convo:new Conversation({ // ...this.getConvoOptionsAsync(tv), // defaultVars:exeCtx.defaultVars, // initConvo:( // (await this.getSharedSourceAsync())+ // (node.sharedConvo?node.sharedConvo+'\n\n':'')+ // transformStep.convo // ) // }) }; } } else { tv.payload = co.result; } } exeCtx.defaultVars['input'] = tv.payload; return null; } async executeStepAsync(tv, node, step, stepIndex, exeCtx) { // lock here const release = await this.stepLock.waitAsync(); try { if (this.hasListeners) { this.triggerEvent({ type: 'execute-step', text: `Executing step ${stepIndex} ${node.key ?? node.id}`, traverser: tv, step: step.nodeStep, stepIndex, node, }); } if (step.nodeStep.resetConvo) { (0, convo_graph_lib_1.resetConvoNodeExecCtxConvo)(exeCtx); } const msgCount = exeCtx.convo.messages.length; exeCtx.convo.append(step.nodeStep.convo); const call = await this.callTargetAsync(step.nodeStep.name ?? `Step ${stepIndex}`, tv, exeCtx.convo, msgCount, stepIndex === exeCtx.steps.length - 1); if (call) { tv.payload = call.value; exeCtx.defaultVars['input'] = tv.payload; const stepKey = stepIndex === -1 ? 'stepAuto' : `step${stepIndex}`; exeCtx.defaultVars[stepKey] = tv.payload; if (step.nodeStep.name) { exeCtx.defaultVars[step.nodeStep.name] = tv.payload; } } return call; } finally { release(); } } async callTargetAsync(name, tv, convo, msgStartIndex, isLast) { const outputFn = (0, convo_lib_1.getConvoFnByTag)(convo_lib_1.convoTags.output, convo.messages, msgStartIndex); const errFn = (0, convo_lib_1.getConvoFnByTag)(convo_lib_1.convoTags.errorCallback, convo.messages, msgStartIndex); const result = await convo.completeAsync({ returnOnCalled: isLast, toolChoice: outputFn ? { name: outputFn.name } : undefined }); const call = result.lastFnCall; if (this.hasListeners) { this.triggerEvent({ type: 'convo-result', text: convo.convo, traverser: tv, }); } if (!call && (outputFn || errFn)) { tv.exeState = 'failed'; tv.errorMessage = `${name} function not called`; return undefined; } const isSuccess = call?.message.tags?.some(t => t.name === convo_lib_1.convoTags.output); const isError = call?.message.tags?.some(t => t.name === convo_lib_1.convoTags.errorCallback); if (call && (isSuccess || isError)) { if (isError) { tv.exeState = 'failed'; tv.errorMessage = (`${name} failed. Error callback called.` + ((typeof call.returnValue === 'string') ? ' ' + call.returnValue : '')); return undefined; } return { call, value: call.returnValue, type: call.fn.returnType, }; } else if (!outputFn) { if (call) { return { call, value: call.returnValue, type: call.fn.returnType, }; } else { const value = (result.message?.format === 'json') ? JSON.parse(result.message.content?.trim() || 'null') : result.message?.content; return { value, type: typeof value, }; } } else { tv.exeState = 'failed'; tv.errorMessage = `${name} failed. function not called`; return undefined; } } } exports.ConvoGraphCtrl = ConvoGraphCtrl; //# sourceMappingURL=ConvoGraphCtrl.js.map