UNPKG

@convo-lang/convo-lang

Version:
1,248 lines (1,247 loc) 164 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Conversation = void 0; const common_1 = require("@iyio/common"); const json5_1 = require("@iyio/json5"); const rxjs_1 = require("rxjs"); const ConvoError_1 = require("./ConvoError"); const ConvoExecutionContext_1 = require("./ConvoExecutionContext"); const ConvoRoom_1 = require("./ConvoRoom"); const HttpConvoCompletionService_1 = require("./HttpConvoCompletionService"); const convo_cached_parsing_1 = require("./convo-cached-parsing"); const convo_completion_lib_1 = require("./convo-completion-lib"); const convo_component_lib_1 = require("./convo-component-lib"); const convo_eval_1 = require("./convo-eval"); const convo_lang_lock_1 = require("./convo-lang-lock"); const convo_lib_1 = require("./convo-lib"); const convo_parser_1 = require("./convo-parser"); const convo_rag_lib_1 = require("./convo-rag-lib"); const convo_template_1 = require("./convo-template"); const convo_types_1 = require("./convo-types"); const convo_zod_1 = require("./convo-zod"); const convo_deps_1 = require("./convo.deps"); const convoAsync_1 = require("./convoAsync"); const createConvoVisionFunction_1 = require("./createConvoVisionFunction"); const convoScopeFunctionEvalJavascript_1 = require("./scope-functions/convoScopeFunctionEvalJavascript"); let nextInstanceId = 1; class Conversation { _convo = []; get convo() { return this._convo.join('\n\n'); } getConvoStrings() { return [...this._convo]; } instanceId; name; room; isAgent; childDepth; inlineHost; inlinePrompt; _messages = []; get messages() { return this._messages; } _onAppend = new rxjs_1.Subject(); get onAppend() { return this._onAppend; } _openTasks = new rxjs_1.BehaviorSubject([]); get openTasksSubject() { return this._openTasks; } get openTasks() { return this._openTasks.value; } _activeTaskCount = new rxjs_1.BehaviorSubject(0); get activeTaskCountSubject() { return this._activeTaskCount; } get activeTaskCount() { return this._activeTaskCount.value; } _trackTime; get trackTimeSubject() { return this._trackTime; } get trackTime() { return this._trackTime.value; } set trackTime(value) { if (value == this._trackTime.value) { return; } this._trackTime.next(value); } _trackTokens; get trackTokensSubject() { return this._trackTokens; } get trackTokens() { return this._trackTokens.value; } set trackTokens(value) { if (value == this._trackTokens.value) { return; } this._trackTokens.next(value); } _onTokenUsage; /** * Tracks the token usages of the Conversation */ usage; _trackModel; get trackModelSubject() { return this._trackModel; } get trackModel() { return this._trackModel.value; } set trackModel(value) { if (value == this._trackModel.value) { return; } this._trackModel.next(value); } _debugMode = new rxjs_1.BehaviorSubject(false); get debugModeSubject() { return this._debugMode; } get debugMode() { return this._debugMode.value; } set debugMode(value) { if (value == this._debugMode.value) { return; } this._debugMode.next(value); } _flat = new rxjs_1.BehaviorSubject(null); get flatSubject() { return this._flat; } /** * A reference to the last flattening of the conversation */ get flat() { return this._flat.value; } _subTasks = new rxjs_1.BehaviorSubject([]); get subTasksSubject() { return this._subTasks; } get subTasks() { return this._subTasks.value; } _beforeAppend = new rxjs_1.Subject(); get beforeAppend() { return this._beforeAppend; } /** * Unregistered variables will be available during execution but will not be added to the code * of the conversation. For example the __cwd var is often used to set the current working * directory but is not added to the conversation code. * * @note shares the same functionality as defaultVars. Maybe remove */ unregisteredVars = {}; userRoles; assistantRoles; systemRoles; roleMap; completionService; converters; maxAutoCompleteDepth; beforeCreateExeCtx; externFunctions = {}; components; /** * Array of modules that can be imported. The modules are not automatically imported, they have * to be imported to take effect. */ modules; /** * The default capabilities of the conversation. Additional capabilities can be enabled by * the first and last message of the conversation as long a disableMessageCapabilities is not * true. */ capabilities; /** * If true capabilities enabled by message in the conversation will be ignored. */ disableMessageCapabilities; /** * Capabilities that should be enabled by the underlying completion service. */ serviceCapabilities; disableAutoFlatten; autoFlattenDelayMs; disableTriggers; /** * Prevents transforms from being applied */ disableTransforms; dynamicFunctionCallback; componentCompletionCallback; /** * A callback used to implement rag retrieval. */ ragCallback; getStartOfConversation; /** * A function that will be used to output debug values. By default debug information is written * to the output of the conversation as comments */ debug; print = convo_lib_1.defaultConvoPrintFunction; defaultOptions; defaultVars; cache; /** * If true the flattened version of the conversation will be logged before sending to LLMs. * @note Cached responses are not logged. Use `logFlatCached` to log both cached and non-cached * flattened conversations. */ logFlat; /** * If true both cached and non-cached versions of the conversation are logged before being set * to an LLM */ logFlatCached; defaultModel; /** * Sub conversations */ subs = {}; constructor(options = {}) { const { name = convo_lib_1.defaultConversationName, isAgent = false, room = new ConvoRoom_1.ConvoRoom(), userRoles = ['user'], assistantRoles = ['assistant'], systemRoles = ['system'], roleMap = {}, completionService = convo_deps_1.convoCompletionService.all(), converters = convo_deps_1.convoConversationConverterProvider.all(), defaultModel = convo_deps_1.convoDefaultModelParam.get(), capabilities = [], serviceCapabilities = [], maxAutoCompleteDepth = 100, trackTime = false, trackTokens = false, trackModel = false, onTokenUsage, disableAutoFlatten = false, disableTransforms = false, autoFlattenDelayMs = 30, ragCallback, debug, debugMode, disableMessageCapabilities = false, initConvo, defaultVars, onConstructed, define, onComponentMessages, componentCompletionCallback, externFunctions, components, externScopeFunctions, getStartOfConversation, cache, logFlat = false, logFlatCached = false, usage = (0, convo_lib_1.createEmptyConvoTokenUsage)(), beforeCreateExeCtx, modules, childDepth = 0, disableTriggers = false, inlineHost, inlinePrompt, } = options; this.instanceId = nextInstanceId++; this.name = name; this.usage = usage; this.isAgent = isAgent; this.defaultOptions = options; this.childDepth = childDepth; this.disableTriggers = disableTriggers; this.beforeCreateExeCtx = beforeCreateExeCtx; this.getStartOfConversation = getStartOfConversation; this.inlineHost = inlineHost; this.inlinePrompt = inlinePrompt; this.cache = typeof cache === 'boolean' ? (cache ? [(0, convo_deps_1.convoCacheService)()] : []) : (0, common_1.asArray)(cache); this.logFlat = logFlat; this.logFlatCached = logFlatCached; this.defaultVars = defaultVars ? defaultVars : {}; this.userRoles = [...userRoles]; this.assistantRoles = [...assistantRoles]; this.systemRoles = [...systemRoles]; this.roleMap = roleMap; if (Array.isArray(completionService) && completionService.length === 1) { this.completionService = completionService[0]; } else { this.completionService = completionService; } this.converters = converters; this.capabilities = [...capabilities]; this.defaultModel = defaultModel ?? undefined; this.disableMessageCapabilities = disableMessageCapabilities; this.serviceCapabilities = serviceCapabilities; this.maxAutoCompleteDepth = maxAutoCompleteDepth; this._trackTime = new rxjs_1.BehaviorSubject(trackTime); this._trackTokens = new rxjs_1.BehaviorSubject(trackTokens); this._trackModel = new rxjs_1.BehaviorSubject(trackModel); this._onTokenUsage = onTokenUsage; this.disableAutoFlatten = disableAutoFlatten; this.disableTransforms = disableTransforms; this.autoFlattenDelayMs = autoFlattenDelayMs; this.componentCompletionCallback = componentCompletionCallback; this.ragCallback = ragCallback; this.debug = debug; if (debugMode) { this.debugMode = true; } this.room = room; this.room.addConversation(this); if (initConvo) { this.append(initConvo, true); } if (define) { this.define(define); } if (onComponentMessages) { if (Array.isArray(onComponentMessages)) { for (const cb of onComponentMessages) { this.watchComponentMessages(cb); } } else { this.watchComponentMessages(onComponentMessages); } } this.components = { ...components }; this.modules = modules ? [...modules] : []; if (externFunctions) { for (const e in externFunctions) { const f = externFunctions[e]; if (f) { this.implementExternFunction(e, f); } } } if (externScopeFunctions) { for (const e in externScopeFunctions) { const f = externScopeFunctions[e]; if (f) { this.externFunctions[e] = f; } } } onConstructed?.(this); } initMessageReady() { const last = this.getLastMessage(); return last?.role === convo_lib_1.convoRoles.user && (0, convo_lib_1.containsConvoTag)(last.tags, convo_lib_1.convoTags.init) ? true : false; } addTask(task) { this._activeTaskCount.next(this._activeTaskCount.value + 1); (0, common_1.pushBehaviorSubjectAry)(this._openTasks, task); const removeFromHost = this.inlineHost?.addTask(task); let removed = false; return () => { if (removed) { return; } removed = true; this._activeTaskCount.next(this._activeTaskCount.value - 1); (0, common_1.removeBehaviorSubjectAryValue)(this._openTasks, task); removeFromHost?.(); }; } popTask() { const task = this._openTasks.value[this._openTasks.value.length - 1]; if (task) { (0, common_1.removeBehaviorSubjectAryValue)(this._openTasks, task); this._activeTaskCount.next(this._activeTaskCount.value - 1); if (this.inlineHost?._openTasks.value.includes(task)) { (0, common_1.removeBehaviorSubjectAryValue)(this.inlineHost._openTasks, task); this.inlineHost._activeTaskCount.next(this.inlineHost._activeTaskCount.value - 1); } } } watchComponentMessages(callback) { return this._flat.subscribe(flat => { if (!flat) { return; } const all = flat.messages.filter(m => m.component); const state = { last: all[all.length - 1], all, flat, convo: this }; if (typeof callback === 'function') { callback(state); } else { callback.next(state); } }); } getMessageListCapabilities(msgs) { let firstMsg; let lastMsg; for (let i = 0; i < msgs.length; i++) { const f = msgs[i]; if (f && (!f.fn || f.fn.topLevel)) { firstMsg = f; break; } } for (let i = msgs.length - 1; i >= 0; i--) { const f = msgs[i]; if (f && (!f.fn || f.fn.topLevel)) { lastMsg = f; break; } } if (!firstMsg && !lastMsg) { return []; } if (firstMsg === lastMsg) { lastMsg = undefined; } const tags = []; if (firstMsg?.tags) { const t = (0, convo_lib_1.mapToConvoTags)(firstMsg.tags); tags.push(...t); } if (lastMsg?.tags) { const t = (0, convo_lib_1.mapToConvoTags)(lastMsg.tags); tags.push(...t); } return this.getMessageCapabilities(tags) ?? []; } /** * Gets the capabilities enabled by the given tags. If disableMessageCapabilities is true * undefined is always returned */ getMessageCapabilities(tags) { if (!tags || this.disableMessageCapabilities) { return undefined; } let capList; for (const tag of tags) { switch (tag.name) { case convo_lib_1.convoTags.capability: if (tag.value) { const caps = tag.value.split(','); for (const c of caps) { const cap = c.trim(); if ((0, convo_types_1.isConvoCapability)(cap) && !capList?.includes(cap)) { if (!capList) { capList = []; } capList.push(cap); } } } break; case convo_lib_1.convoTags.enableVision: if (!capList?.includes('vision')) { if (!capList) { capList = []; } capList.push('vision'); } break; case convo_lib_1.convoTags.enabledVisionFunction: if (!capList?.includes('visionFunction')) { if (!capList) { capList = []; } capList.push('visionFunction'); } break; } } if (!capList) { return undefined; } for (const cap of capList) { this.enableCapability(cap); } return capList; } _isDisposed = false; _disposeToken = new common_1.CancelToken(); get disposeToken() { return this._disposeToken; } get isDisposed() { return this._isDisposed; } dispose() { if (this._isDisposed) { return; } this.autoFlattenId++; this._isDisposed = true; this._disposeToken.cancelNow(); this.room.removeConversation(this); } _defaultApiKey = null; setDefaultApiKey(key) { this._defaultApiKey = key; } getDefaultApiKey() { if (typeof this._defaultApiKey === 'function') { return this._defaultApiKey(); } else { return this._defaultApiKey; } } parseCode(code) { return (0, convo_parser_1.parseConvoCode)(code, { parseMarkdown: this.defaultOptions.parseMarkdown, logErrors: true }); } enabledCapabilities = []; enableCapability(cap) { if (this.enabledCapabilities.includes(cap)) { return; } this.enabledCapabilities.push(cap); switch (cap) { case 'visionFunction': this.define({ hidden: true, fn: (0, createConvoVisionFunction_1.createConvoVisionFunction)() }, true); break; case 'vision': if (!this.serviceCapabilities.includes("vision")) { this.serviceCapabilities.push('vision'); } break; } } autoUpdateCompletionService() { if (!this.completionService) { this.completionService = convo_deps_1.convoCompletionService.get(); } } createChild(options) { const convo = new Conversation(this.getCloneOptions(options)); if (this._defaultApiKey) { convo.setDefaultApiKey(this._defaultApiKey); } return convo; } getCloneOptions(options) { return { ...this.defaultOptions, childDepth: this.childDepth + 1, debug: this.debugToConversation, debugMode: this.shouldDebug(), beforeCreateExeCtx: this.beforeCreateExeCtx, ...options, defaultVars: { ...this.defaultVars, ...options?.defaultVars }, externScopeFunctions: { ...this.externFunctions }, components: { ...this.components }, modules: [...this.modules], }; } /** * Creates a new Conversation and appends the messages of this conversation to the newly * created conversation. */ clone({ inlinePrompt, triggerName, empty = (inlinePrompt && !inlinePrompt.extend && !inlinePrompt.continue), noFunctions, systemOnly, removeAgents, dropLast = inlinePrompt?.dropLast, dropUntilContent = inlinePrompt ? true : false, last = inlinePrompt?.last, cloneConvoString, } = {}, convoOptions) { const cloneOptions = this.getCloneOptions(convoOptions); if (inlinePrompt) { delete cloneOptions.debug; cloneOptions.inlinePrompt = inlinePrompt; cloneOptions.inlineHost = this; cloneOptions.disableTriggers = true; cloneOptions.disableAutoFlatten = true; } const conversation = new Conversation(cloneOptions); if (this._defaultApiKey) { conversation.setDefaultApiKey(this._defaultApiKey); } let messages = empty ? [] : [...this.messages]; if (inlinePrompt && triggerName) { this.filterConvoMessagesForTrigger(inlinePrompt, triggerName, messages); } if (noFunctions) { messages = messages.filter(m => !m.fn || m.fn.topLevel); } if (systemOnly) { messages = messages.filter(m => m.role === 'system' || m.fn?.topLevel || m.fn?.name === convo_lib_1.convoFunctions.getState); } if (removeAgents) { messages = messages.filter(m => m.tags?.some(t => t.name === convo_lib_1.convoTags.agentSystem) || (m.fn && this.agents.some(a => a.name === m.fn?.name))); for (const agent of this.agents) { delete conversation.defaultVars[agent.name]; } } else { for (const agent of this.agents) { conversation.agents.push(agent); } } for (const name in this.importedModules) { conversation.importedModules[name] = this.importedModules[name]; } if (dropUntilContent) { while (messages.length && !this.isContentMessage(messages[messages.length - 1])) { messages.pop(); } } if (dropLast !== undefined) { messages.splice(messages.length - dropLast, dropLast); } if (last !== undefined) { messages.splice(0, messages.length - last); } if (triggerName) { messages.push(...(0, convo_cached_parsing_1.requireParseConvoCached)(`> define\n${convo_lib_1.convoFunctions.clearRag}()`)); } conversation.appendMessageObject(messages, { disableAutoFlatten: true, appendCode: cloneConvoString }); return conversation; } filterConvoMessagesForTrigger(prompt, triggerName, messages) { const { system, functions, } = prompt; for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (!msg) { messages.splice(i, 1); i--; continue; } const exclude = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.excludeFromTriggers); const include = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.includeInTriggers); if (((!system && this.isSystemMessage(msg)) || (!functions && msg.fn && !msg.fn.call && !msg.fn.topLevel) || (exclude && (exclude.value === undefined || exclude.value === triggerName))) && !((include && (include.value === undefined || include.value === triggerName)))) { messages.splice(i, 1); i--; continue; } } } /** * Creates a new Conversation and appends the system messages of this conversation to the newly * created conversation. */ cloneSystem() { return this.clone({ systemOnly: true }); } /** * Creates a new Conversation and appends the non-function messages of this conversation to the newly * created conversation. */ cloneWithNoFunctions() { return this.clone({ noFunctions: true }); } appendMsgsAry(messages, index = this._messages.length) { let depth = 0; let subName; let subs; let endRole; let subType; let head; let imp; for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (!msg) { continue; } if (msg.role === endRole) { depth--; if (!depth) { const sub = this.room.state.lookup[subName ?? ''] ?? this.createChild({ room: this.room }); for (const agent of this.agents) { delete sub.externFunctions[agent.name]; } if (head) { if (!imp) { imp = []; } switch (subType) { case convo_lib_1.convoMsgModifiers.agent: imp.push({ subs: subs ?? [], subType: subType, head, convo: sub }); break; } } endRole = undefined; subName = undefined; subs = undefined; subType = undefined; head = undefined; continue; } } else if (msg.fn) { const mod = msg.fn.modifiers?.find(m => convo_lib_1.convoScopedModifiers.includes(m)); if (mod) { depth++; if (depth === 1) { subType = mod; endRole = mod + 'End'; subName = msg.fn.name; subs = []; head = msg; continue; } } } if (subs) { subs.push(msg); } else { this._messages.splice(index, 0, msg); index++; } } if (depth) { throw new Error(`Sub-conversation not ended - name=${subName}`); } if (imp) { for (const i of imp) { switch (i.subType) { case 'agent': this.defineAgent(i.convo, i.head, i.subs); } } } } defineAgent(conversation, headMsg, messages) { headMsg = { ...headMsg }; if (!headMsg.fn) { return; } const hasAgentSystem = this._messages.some(m => m.tags?.some(t => t.name === convo_lib_1.convoTags.agentSystem)); if (!hasAgentSystem) { this.append(`@${convo_lib_1.convoTags.agentSystem}\n> system\nYou can use the following agents to assistant the user.\n` + '{{getAgentList()}}\n\n' + 'To send a request to an agent either call the function with the same name as the agent or ' + 'call a more specialized function that starts with the agents name, but DO NOT call both.'); } const agent = { name: headMsg.fn.name === this.name ? this.name + '_2' : headMsg.fn.name, description: headMsg.description, main: headMsg, capabilities: [], functions: [] }; if (headMsg.tags) { for (const tag of headMsg.tags) { if (tag.name === convo_lib_1.convoTags.cap && tag.value) { agent.capabilities?.push(tag.value); } } } headMsg.fn = { ...headMsg.fn }; headMsg.fn.modifiers = [...headMsg.fn.modifiers]; headMsg.fn.local = true; (0, common_1.aryRemoveItem)(headMsg.fn.modifiers, convo_lib_1.convoMsgModifiers.agent); messages.push(headMsg); const proxyFn = { ...headMsg }; if (proxyFn.fn) { proxyFn.fn = { ...proxyFn.fn }; delete proxyFn.fn.body; proxyFn.fn.extern = true; proxyFn.fn.local = false; this.appendMessageObject(proxyFn); this.externFunctions[agent.name] = async (scope) => { if (!headMsg.fn) { return null; } const clone = conversation.clone(); let r = await clone.callFunctionAsync(headMsg.fn.name, (0, convo_lib_1.convoLabeledScopeParamsToObj)(scope), { returnOnCalled: true }); if (!r) { return r; } if (typeof r === 'object') { const keys = Object.keys(r); if (keys.length === 1) { r = r[keys[0] ?? '']; } if (!r) { return r; } } const sub = clone.onAppend.subscribe(a => { this.appendAfterCall.push(a.text.replace(/(^|\n)\s*>/g, text => `\n@cid ${agent.name}\n${text}`)); // todo - append }); try { clone.append((0, convo_template_1.convoScript) `> user\n${r}`); const completion = await clone.completeAsync(); const returnValue = completion.message?.callParams ?? completion.message?.content; if (typeof returnValue === 'string') { return `${agent.name}'s response:\n<agent-response>\n${returnValue}\n</agent-response>`; } else { return returnValue; } } finally { sub.unsubscribe(); } }; } conversation.appendMessageObject(messages); for (const msg of messages) { if (!msg.fn || msg === headMsg || !msg.fn.modifiers.includes('public')) { continue; } // add to description - When call the agent "Max" will handle the execution of the function. // create proxy } // create proxy for any public functions this.agents.push(agent); } appendAfterCall = []; agents = []; /** * Appends new messages to the conversation and by default does not add code to the conversation. */ appendMessageObject(message, { disableAutoFlatten, appendCode, source, } = {}) { const messages = (0, common_1.asArray)(message); this.appendMsgsAry(messages); if (source) { if (this._beforeAppend.observed) { source = this.transformMessageBeforeAppend(source); } this._convo.push(source); } else if (appendCode) { for (const msg of messages) { source = (0, convo_lib_1.convoMessageToString)(msg); if (this._beforeAppend.observed) { source = this.transformMessageBeforeAppend(source); } this._convo.push(source); } } this._onAppend.next({ text: source ?? '', messages, }); if (!this.disableAutoFlatten && !disableAutoFlatten) { this.autoFlattenAsync(false); } } transformMessageBeforeAppend(messages) { const append = { text: messages, messages: [] }; this._beforeAppend.next(append); return append.text; } appendDefineVars(vars) { const convo = [`> define`]; for (const name in vars) { const value = vars[name]; if (!(0, convo_lib_1.isValidConvoIdentifier)(name)) { throw new Error(`Invalid var name - ${name}`); } convo.push(`${name} = ${value === undefined ? undefined : JSON.stringify(value, null, 4)}`); } this.append(convo.join('\n')); } appendDefineVar(name, value) { return this.appendDefineVars({ [name]: value }); } append(messages, mergeWithPrevOrOptions = false, _throwOnError = true) { const options = (typeof mergeWithPrevOrOptions === 'object') ? mergeWithPrevOrOptions : { mergeWithPrev: mergeWithPrevOrOptions }; const { mergeWithPrev = false, throwOnError = _throwOnError, disableAutoFlatten, addTags, } = options; if ((0, convoAsync_1.isConvoObject)(messages)) { const outputOptions = messages.getOutputOptions(); for (const e in outputOptions.defaultVars) { const v = outputOptions.defaultVars[e]; if (v === undefined) { continue; } this.defaultVars[e] = v; } if (outputOptions.externFunctions) { for (const name in outputOptions.externFunctions) { const fn = outputOptions.externFunctions[name]; if (!fn) { continue; } this.implementExternFunction(name, fn); } } if (outputOptions.externScopeFunctions) { for (const name in outputOptions.externScopeFunctions) { const fn = outputOptions.externScopeFunctions[name]; if (!fn) { continue; } this.externFunctions[name] = fn; } } messages = messages.getInput(); } let visibleContent = undefined; let hasHidden = false; if (Array.isArray(messages)) { hasHidden = messages.some(m => (typeof m === 'object') ? m.hidden : false); if (hasHidden) { visibleContent = messages.filter(m => (typeof m === 'string') || !m.hidden).map(m => (typeof m === 'string') ? m : m.content).join(''); messages = messages.map(m => (typeof m === 'string') ? m : m.content).join(''); } else { messages = messages.map(m => (typeof m === 'string') ? m : m.content).join(''); } } if (this._beforeAppend.observed) { messages = this.transformMessageBeforeAppend(messages); } const r = this.parseCode(messages); if (r.error) { if (!throwOnError) { return r; } throw r.error; } if (options.filePath && r.result) { for (const m of r.result) { m[convo_types_1.convoMessageSourcePathKey] = options.filePath; } } if (addTags?.length && r.result) { for (const m of r.result) { if (m.tags) { m.tags.push(...addTags); } else { m.tags = [...addTags]; } } } if (hasHidden) { messages = visibleContent ?? ''; } if (messages) { if (mergeWithPrev && this._convo.length) { this._convo[this._convo.length - 1] += '\n' + messages; } else { this._convo.push(messages); } } if (r.result) { this.appendMsgsAry(r.result); } this._onAppend.next({ text: messages, messages: r.result ?? [] }); if (!this.disableAutoFlatten && !disableAutoFlatten) { this.autoFlattenAsync(false); } return r; } autoFlattenId = 0; async autoFlattenAsync(skipDelay) { const id = ++this.autoFlattenId; if (this.autoFlattenDelayMs > 0 && !skipDelay) { await (0, common_1.delayAsync)(this.autoFlattenDelayMs); if (this.isDisposed || id !== this.autoFlattenId) { return undefined; } } return await this.getAutoFlattenPromise(id); } autoFlatPromiseRef = null; getAutoFlattenPromise(id) { if (this.autoFlatPromiseRef?.id === id) { return this.autoFlatPromiseRef.promise; } const promise = this.setFlattenAsync(id); this.autoFlatPromiseRef = { id, promise, }; return promise; } async setFlattenAsync(id) { const flat = await this.flattenAsync(undefined, { setCurrent: false }); if (this.isDisposed || id !== this.autoFlattenId) { return undefined; } this.setFlat(flat, false); return flat; } /** * Get the flattened version of this Conversation. * @param noCache If true the Conversation will not used the current cached version of the * flattening and will be re-flattened. */ async getLastAutoFlatAsync(noCache = false) { if (noCache) { return (await this.autoFlattenAsync(true)) ?? this.flat ?? undefined; } return (this.flat ?? (await this.getAutoFlattenPromise(this.autoFlattenId)) ?? this.flat ?? undefined); } getLastMessage() { return this.messages[this.messages.length - 1]; } getLastUserMessage(messages) { if (!messages) { return undefined; } for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (msg && this.isUserMessage(msg)) { return msg; } } return undefined; } getLastUserOrThinkingMessage(messages) { if (!messages) { return undefined; } for (let i = messages.length - 1; i >= 0; i--) { const msg = messages[i]; if (msg && this.isUserOrThinkingMessage(msg)) { return msg; } } return undefined; } appendUserMessage(message, options) { this.append((0, convo_lib_1.formatConvoMessage)('user', message, this.getPrefixTags(options))); } appendAssistantMessage(message, options) { this.append((0, convo_lib_1.formatConvoMessage)('assistant', message, this.getPrefixTags(options))); } appendMessage(role, message, options) { this.append((0, convo_lib_1.formatConvoMessage)(role, message, this.getPrefixTags(options))); } appendDefine(defineCode, description) { return this.append((description ? (0, convo_lib_1.convoDescriptionToComment)(description) + '\n' : '') + '> define\n' + defineCode); } appendTopLevel(defineCode, description) { return this.append((description ? (0, convo_lib_1.convoDescriptionToComment)(description) + '\n' : '') + '> do\n' + defineCode); } getVar(nameOrPath, defaultValue) { return this._flat.value?.exe?.getVar(nameOrPath, null, defaultValue); } getPrefixTags(options) { let tags = ''; const msg = options?.msg; if (this.trackTime || this.getVar(convo_lib_1.convoVars.__trackTime)) { tags += `@${convo_lib_1.convoTags.time} ${(0, convo_lib_1.getConvoDateString)()}\n`; } if (!msg) { return tags; } if (options?.includeTokenUsage && (this.trackTokens || this.getVar(convo_lib_1.convoVars.__trackTokenUsage))) { tags += `@${convo_lib_1.convoTags.tokenUsage} ${(0, convo_lib_1.convoUsageTokensToString)(msg)}\n`; } if (msg.model && (this.trackModel || this.getVar(convo_lib_1.convoVars.__trackModel))) { tags += `@${convo_lib_1.convoTags.model} ${msg.model}\n`; } if (msg.endpoint) { tags += `@${convo_lib_1.convoTags.endpoint} ${msg.endpoint}\n`; } if (msg.format) { tags += `@${convo_lib_1.convoTags.format} ${msg.format}\n`; } if (msg.assignTo) { tags += `@${convo_lib_1.convoTags.assignTo} ${msg.assignTo}\n`; } return tags; } modelServiceMap = {}; endpointModelServiceMap = {}; async getCompletionServiceAsync(flat) { const services = await (0, convo_completion_lib_1.getConvoCompletionServicesForModelAsync)(flat.responseModel ?? convo_lib_1.convoAnyModelName, this.completionService ? (0, common_1.asArray)(this.completionService) : [], this.modelServiceMap); return services[0]; } setFlat(flat, dup = true) { if (this.isDisposed) { return; } this.autoFlattenId++; this._flat.next(dup ? { ...flat } : flat); } async callFunctionAsync(fn, args = {}, options) { const c = await this.tryCompleteAsync(options?.task, { ...options, returnOnCalled: true }, flat => { if (typeof fn === 'object') { flat.exe.loadFunctions([{ fn, role: 'function' }]); } return [{ callFn: typeof fn === 'string' ? fn : fn.name, callParams: args }]; }); return c.returnValues?.[0]; } appendFunctionCall(functionName, args) { this.append(`@${convo_lib_1.convoTags.toolId} call_${(0, common_1.shortUuid)()}\n> call ${functionName}(${args === undefined ? '' : (0, convo_lib_1.spreadConvoArgs)(args, true)})`); } completeWithFunctionCallAsync(name, args, options) { this.appendFunctionCall(name, args); return this.completeAsync(options); } /** * Appends a user message then competes the conversation * @param append Optional message to append before submitting */ completeUserMessageAsync(userMessage) { this.appendUserMessage(userMessage); return this.completeAsync(); } async completeAsync(appendOrOptions, optionsForAwaitable) { if ((0, convoAsync_1.isConvoObject)(appendOrOptions)) { this.append(appendOrOptions); const completion = await this.completeAsync(optionsForAwaitable); return (0, convo_lib_1.getAssumedConvoCompletionValue)(completion); } if (typeof appendOrOptions === 'string') { this.append(appendOrOptions); appendOrOptions = undefined; } const modelInputOutput = appendOrOptions?.modelInputOutput; if (appendOrOptions?.append) { this.append(appendOrOptions.append); } if (appendOrOptions?.debug) { console.info('Conversation.completeAsync:\n', appendOrOptions.append); } const result = await this.tryCompleteAsync(appendOrOptions?.task, appendOrOptions, async (flat) => { return await this.completeWithServiceAsync(flat, modelInputOutput); }); if (appendOrOptions?.debug) { console.info('Conversation.completeAsync Result:\n', result.messages ? (result.messages.length === 1 ? result.messages[0] : result.messages) : result); } return result; } httpEndpointServices = {}; getHttpService(endpoint) { return this.httpEndpointServices[endpoint] ?? (this.httpEndpointServices[endpoint] = new HttpConvoCompletionService_1.HttpConvoCompletionService({ endpoint })); } async completeWithServiceAsync(flat, modelInputOutput) { //@@with-service const convoEndpoint = flat.exe.getVar(convo_lib_1.convoVars.__convoEndpoint); const serviceAndModel = await (0, convo_completion_lib_1.getConvoCompletionServiceAsync)(flat, (convoEndpoint ? [this.getHttpService(convoEndpoint)] : this.completionService ? (0, common_1.asArray)(this.completionService) : []), true, convoEndpoint ? (this.endpointModelServiceMap[convoEndpoint] ?? (this.endpointModelServiceMap[convoEndpoint] = {})) : this.modelServiceMap); const lastMsg = flat.messages[flat.messages.length - 1]; let cacheType = ((lastMsg?.tags && (convo_lib_1.convoTags.cache in lastMsg.tags) && (lastMsg.tags[convo_lib_1.convoTags.cache] ?? convo_lib_1.defaultConvoCacheType)) ?? flat.exe.getVar(convo_lib_1.convoVars.__cache)); if (this.logFlatCached) { console.info((0, convo_lib_1.getFlattenConversationDisplayString)(flat, true)); } let cache = cacheType ? this.cache?.find(c => c.cacheType === cacheType) : this.cache?.[0]; if (!cache && (cacheType === true || cacheType === convo_lib_1.defaultConvoCacheType)) { cache = (0, convo_deps_1.convoCacheService)(); } if (cache?.getCachedResponse) { const cached = await cache.getCachedResponse(flat); if (cached) { return cached; } } if (this.logFlat) { console.info((0, convo_lib_1.getFlattenConversationDisplayString)(flat, true)); } if (!serviceAndModel) { return []; } this.debug?.('To be completed', flat.messages); const triggerName = flat.exe.getVar(convo_lib_1.convoVars.__trigger); if (this.inlineHost) { const last = this.getLastUserOrThinkingMessage(flat.messages); if (last) { this.inlineHost.append(`> ${convo_lib_1.convoRoles.thinking}${triggerName ? ' ' + triggerName : ''} ${last.role} (${this.inlinePrompt?.header})\n${(0, convo_lib_1.escapeConvo)((0, convo_lib_1.getFullFlatConvoMessageContent)(last))}`, { disableAutoFlatten: true }); } if (flat.exe.getVar(convo_lib_1.convoVars.__debugInline)) { this.inlineHost.appendArgsAsComment('debug thinking', flat.messages, true); } } let configInputResult; if (serviceAndModel.model) { configInputResult = await (0, convo_completion_lib_1.applyConvoModelConfigurationToInputAsync)(serviceAndModel.model, flat, this); } let messages; const lock = (0, convo_lang_lock_1.getGlobalConversationLock)(); const release = await lock?.waitOrCancelAsync(this._disposeToken); if (lock && !release) { return []; } try { if (modelInputOutput !== undefined) { messages = (0, convo_completion_lib_1.requireConvertConvoOutput)(modelInputOutput.output, serviceAndModel.service.outputType, modelInputOutput.input, serviceAndModel.service.inputType, this.converters, flat); } else { messages = await (0, convo_completion_lib_1.completeConvoUsingCompletionServiceAsync)(flat, serviceAndModel.service, this.converters); } } finally { release?.(); } this.debug?.('Completion message', messages); if (serviceAndModel.model && configInputResult) { (0, convo_completion_lib_1.applyConvoModelConfigurationToOutput)(serviceAndModel.model, flat, messages, configInputResult); } if (this.inlineHost) { this.inlineHost.append(messages.map(m => `> ${convo_lib_1.convoRoles.thinking}${triggerName ? ' ' + triggerName : ''} ${m.role}\n${(0, convo_lib_1.escapeConvo)(m.content)}`), { disableAutoFlatten: true }); if (flat.exe.getVar(convo_lib_1.convoVars.__debugInline)) { this.inlineHost.appendArgsAsComment('debug thinking response', messages, true); } } if (cache?.cachedResponse) { await cache.cachedResponse(flat, messages); } return messages; } /** * Completes the conversation and returns the last message as JSON. It is recommended using * `@json` mode with the last message that is appended. */ async completeJsonAsync(appendOrOptions) { const r = await this.completeAsync(appendOrOptions); if (r.message?.content === undefined) { return undefined; } try { return (0, convo_lib_1.parseConvoJsonMessage)(r.message.content); } catch { return undefined; } } /** * Completes the conversation and returns the last message as JSON. It is recommended using * `@json` mode with the last message that is appended. */ async completeJsonSchemeAsync(params, userMessage) { const r = await this.completeAsync(/*convo*/ ` > define JsonScheme=${(0, convo_zod_1.zodSchemeToConvoTypeString)(params)} @json JsonScheme > user ${(0, convo_lib_1.escapeConvoMessageContent)(userMessage)} `); if (r.message?.content === undefined) { return undefined; } try { return (0, convo_lib_1.parseConvoJsonMessage)(r.message.content); } catch { return undefined; } } _onCompletionStart = new rxjs_1.Subject(); /** * Occurs at the start of a public completion. */ get onCompletionStart() { return this._onCompletionStart; } /** * Completes the conversation and returns the last message call params. The last message of the * conversation should instruct the LLM to call a function. */ async callStubFunctionAsync(appendOrOptions) { if (appendOrOptions === undefined) { appendOrOptions = {}; } else if (typeof appendOrOptions === 'string') { appendOrOptions = { append: appendOrOptions }; } appendOrOptions.returnOnCall = true; const r = await this.completeAsync(appendOrOptions); return r.message?.callParams; } async tryCompleteAsync(task, additionalOptions, getCompletion, autoCompleteDepth = 0, prevCompletion, preReturnValues) { if (this._isCompleting.value) { return { status: 'busy', messages: [], task: task ?? convo_lib_1.defaultConvoTask, }; } else { this._isCompleting.next(true); try { const completionPromise = this._completeAsync(undefined, true, additionalOptions?.usage, task, additionalOptions, getCompletion, autoCompleteDepth, prevCompletion, preReturnValues); this._onCompletionStart.next({ convo: this, completionPromise, options: additionalOptions, task }); return await completionPromise; } finally { this._isCompleting.next(false); } } } async completeParallelAsync(flat, options) { const messages = flat.parallelMessages; if (!messages || messages.length < 2) { return undefined; } const startIndex = this.messages.indexOf(messages[0]); if (startIndex === -1) { return undefined; } const c = await this.completeParallelMessagesAsync(messages, flat.messages.slice(flat.messages.length - messages.length).map(m => m.label), startIndex, options, flat.queueRef ? true : false); return c; } async getModelsAsync(serviceOrId) { const service = (typeof serviceOrId === 'string') ? (0, common_1.asArray)(this.completionService)?.find(s => s.serviceId === serviceOrId) : serviceOrId; if (!service) { return []; } return await (0, convo_lib_1.getConvoCompletionServiceModelsAsync)(service); } async getAllModelsAsync() { if (!this.completionService) { return []; } const models = []; const ary = (0, common_1.asArray)(this.completionService); for (const s of ary) { const m = await (0, convo_lib_1.getConvoCompletionServiceModelsAsync)(s); models.push(...m); } return models; } async completeParallelMessagesAsync(messages, labels, startIndex, options, inQueue) { const all = await Promise.all(messages.map(async (msg, i) => { c