291 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			291 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | "use strict"; | ||
|  | var _AbstractChatCompletionRunner_instances, _AbstractChatCompletionRunner_getFinalContent, _AbstractChatCompletionRunner_getFinalMessage, _AbstractChatCompletionRunner_getFinalFunctionToolCall, _AbstractChatCompletionRunner_getFinalFunctionToolCallResult, _AbstractChatCompletionRunner_calculateTotalUsage, _AbstractChatCompletionRunner_validateParams, _AbstractChatCompletionRunner_stringifyFunctionCallResult; | ||
|  | Object.defineProperty(exports, "__esModule", { value: true }); | ||
|  | exports.AbstractChatCompletionRunner = void 0; | ||
|  | const tslib_1 = require("../internal/tslib.js"); | ||
|  | const error_1 = require("../error.js"); | ||
|  | const parser_1 = require("../lib/parser.js"); | ||
|  | const chatCompletionUtils_1 = require("./chatCompletionUtils.js"); | ||
|  | const EventStream_1 = require("./EventStream.js"); | ||
|  | const RunnableFunction_1 = require("./RunnableFunction.js"); | ||
|  | const DEFAULT_MAX_CHAT_COMPLETIONS = 10; | ||
|  | class AbstractChatCompletionRunner extends EventStream_1.EventStream { | ||
|  |     constructor() { | ||
|  |         super(...arguments); | ||
|  |         _AbstractChatCompletionRunner_instances.add(this); | ||
|  |         this._chatCompletions = []; | ||
|  |         this.messages = []; | ||
|  |     } | ||
|  |     _addChatCompletion(chatCompletion) { | ||
|  |         this._chatCompletions.push(chatCompletion); | ||
|  |         this._emit('chatCompletion', chatCompletion); | ||
|  |         const message = chatCompletion.choices[0]?.message; | ||
|  |         if (message) | ||
|  |             this._addMessage(message); | ||
|  |         return chatCompletion; | ||
|  |     } | ||
|  |     _addMessage(message, emit = true) { | ||
|  |         if (!('content' in message)) | ||
|  |             message.content = null; | ||
|  |         this.messages.push(message); | ||
|  |         if (emit) { | ||
|  |             this._emit('message', message); | ||
|  |             if ((0, chatCompletionUtils_1.isToolMessage)(message) && message.content) { | ||
|  |                 // Note, this assumes that {role: 'tool', content: …} is always the result of a call of tool of type=function.
 | ||
|  |                 this._emit('functionToolCallResult', message.content); | ||
|  |             } | ||
|  |             else if ((0, chatCompletionUtils_1.isAssistantMessage)(message) && message.tool_calls) { | ||
|  |                 for (const tool_call of message.tool_calls) { | ||
|  |                     if (tool_call.type === 'function') { | ||
|  |                         this._emit('functionToolCall', tool_call.function); | ||
|  |                     } | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |     } | ||
|  |     /** | ||
|  |      * @returns a promise that resolves with the final ChatCompletion, or rejects | ||
|  |      * if an error occurred or the stream ended prematurely without producing a ChatCompletion. | ||
|  |      */ | ||
|  |     async finalChatCompletion() { | ||
|  |         await this.done(); | ||
|  |         const completion = this._chatCompletions[this._chatCompletions.length - 1]; | ||
|  |         if (!completion) | ||
|  |             throw new error_1.OpenAIError('stream ended without producing a ChatCompletion'); | ||
|  |         return completion; | ||
|  |     } | ||
|  |     /** | ||
|  |      * @returns a promise that resolves with the content of the final ChatCompletionMessage, or rejects | ||
|  |      * if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage. | ||
|  |      */ | ||
|  |     async finalContent() { | ||
|  |         await this.done(); | ||
|  |         return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalContent).call(this); | ||
|  |     } | ||
|  |     /** | ||
|  |      * @returns a promise that resolves with the the final assistant ChatCompletionMessage response, | ||
|  |      * or rejects if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage. | ||
|  |      */ | ||
|  |     async finalMessage() { | ||
|  |         await this.done(); | ||
|  |         return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this); | ||
|  |     } | ||
|  |     /** | ||
|  |      * @returns a promise that resolves with the content of the final FunctionCall, or rejects | ||
|  |      * if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage. | ||
|  |      */ | ||
|  |     async finalFunctionToolCall() { | ||
|  |         await this.done(); | ||
|  |         return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCall).call(this); | ||
|  |     } | ||
|  |     async finalFunctionToolCallResult() { | ||
|  |         await this.done(); | ||
|  |         return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCallResult).call(this); | ||
|  |     } | ||
|  |     async totalUsage() { | ||
|  |         await this.done(); | ||
|  |         return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_calculateTotalUsage).call(this); | ||
|  |     } | ||
|  |     allChatCompletions() { | ||
|  |         return [...this._chatCompletions]; | ||
|  |     } | ||
|  |     _emitFinal() { | ||
|  |         const completion = this._chatCompletions[this._chatCompletions.length - 1]; | ||
|  |         if (completion) | ||
|  |             this._emit('finalChatCompletion', completion); | ||
|  |         const finalMessage = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this); | ||
|  |         if (finalMessage) | ||
|  |             this._emit('finalMessage', finalMessage); | ||
|  |         const finalContent = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalContent).call(this); | ||
|  |         if (finalContent) | ||
|  |             this._emit('finalContent', finalContent); | ||
|  |         const finalFunctionCall = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCall).call(this); | ||
|  |         if (finalFunctionCall) | ||
|  |             this._emit('finalFunctionToolCall', finalFunctionCall); | ||
|  |         const finalFunctionCallResult = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCallResult).call(this); | ||
|  |         if (finalFunctionCallResult != null) | ||
|  |             this._emit('finalFunctionToolCallResult', finalFunctionCallResult); | ||
|  |         if (this._chatCompletions.some((c) => c.usage)) { | ||
|  |             this._emit('totalUsage', tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_calculateTotalUsage).call(this)); | ||
|  |         } | ||
|  |     } | ||
|  |     async _createChatCompletion(client, params, options) { | ||
|  |         const signal = options?.signal; | ||
|  |         if (signal) { | ||
|  |             if (signal.aborted) | ||
|  |                 this.controller.abort(); | ||
|  |             signal.addEventListener('abort', () => this.controller.abort()); | ||
|  |         } | ||
|  |         tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_validateParams).call(this, params); | ||
|  |         const chatCompletion = await client.chat.completions.create({ ...params, stream: false }, { ...options, signal: this.controller.signal }); | ||
|  |         this._connected(); | ||
|  |         return this._addChatCompletion((0, parser_1.parseChatCompletion)(chatCompletion, params)); | ||
|  |     } | ||
|  |     async _runChatCompletion(client, params, options) { | ||
|  |         for (const message of params.messages) { | ||
|  |             this._addMessage(message, false); | ||
|  |         } | ||
|  |         return await this._createChatCompletion(client, params, options); | ||
|  |     } | ||
|  |     async _runTools(client, params, options) { | ||
|  |         const role = 'tool'; | ||
|  |         const { tool_choice = 'auto', stream, ...restParams } = params; | ||
|  |         const singleFunctionToCall = typeof tool_choice !== 'string' && tool_choice.type === 'function' && tool_choice?.function?.name; | ||
|  |         const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS } = options || {}; | ||
|  |         // TODO(someday): clean this logic up
 | ||
|  |         const inputTools = params.tools.map((tool) => { | ||
|  |             if ((0, parser_1.isAutoParsableTool)(tool)) { | ||
|  |                 if (!tool.$callback) { | ||
|  |                     throw new error_1.OpenAIError('Tool given to `.runTools()` that does not have an associated function'); | ||
|  |                 } | ||
|  |                 return { | ||
|  |                     type: 'function', | ||
|  |                     function: { | ||
|  |                         function: tool.$callback, | ||
|  |                         name: tool.function.name, | ||
|  |                         description: tool.function.description || '', | ||
|  |                         parameters: tool.function.parameters, | ||
|  |                         parse: tool.$parseRaw, | ||
|  |                         strict: true, | ||
|  |                     }, | ||
|  |                 }; | ||
|  |             } | ||
|  |             return tool; | ||
|  |         }); | ||
|  |         const functionsByName = {}; | ||
|  |         for (const f of inputTools) { | ||
|  |             if (f.type === 'function') { | ||
|  |                 functionsByName[f.function.name || f.function.function.name] = f.function; | ||
|  |             } | ||
|  |         } | ||
|  |         const tools = 'tools' in params ? | ||
|  |             inputTools.map((t) => t.type === 'function' ? | ||
|  |                 { | ||
|  |                     type: 'function', | ||
|  |                     function: { | ||
|  |                         name: t.function.name || t.function.function.name, | ||
|  |                         parameters: t.function.parameters, | ||
|  |                         description: t.function.description, | ||
|  |                         strict: t.function.strict, | ||
|  |                     }, | ||
|  |                 } | ||
|  |                 : t) | ||
|  |             : undefined; | ||
|  |         for (const message of params.messages) { | ||
|  |             this._addMessage(message, false); | ||
|  |         } | ||
|  |         for (let i = 0; i < maxChatCompletions; ++i) { | ||
|  |             const chatCompletion = await this._createChatCompletion(client, { | ||
|  |                 ...restParams, | ||
|  |                 tool_choice, | ||
|  |                 tools, | ||
|  |                 messages: [...this.messages], | ||
|  |             }, options); | ||
|  |             const message = chatCompletion.choices[0]?.message; | ||
|  |             if (!message) { | ||
|  |                 throw new error_1.OpenAIError(`missing message in ChatCompletion response`); | ||
|  |             } | ||
|  |             if (!message.tool_calls?.length) { | ||
|  |                 return; | ||
|  |             } | ||
|  |             for (const tool_call of message.tool_calls) { | ||
|  |                 if (tool_call.type !== 'function') | ||
|  |                     continue; | ||
|  |                 const tool_call_id = tool_call.id; | ||
|  |                 const { name, arguments: args } = tool_call.function; | ||
|  |                 const fn = functionsByName[name]; | ||
|  |                 if (!fn) { | ||
|  |                     const content = `Invalid tool_call: ${JSON.stringify(name)}. Available options are: ${Object.keys(functionsByName) | ||
|  |                         .map((name) => JSON.stringify(name)) | ||
|  |                         .join(', ')}. Please try again`;
 | ||
|  |                     this._addMessage({ role, tool_call_id, content }); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 else if (singleFunctionToCall && singleFunctionToCall !== name) { | ||
|  |                     const content = `Invalid tool_call: ${JSON.stringify(name)}. ${JSON.stringify(singleFunctionToCall)} requested. Please try again`; | ||
|  |                     this._addMessage({ role, tool_call_id, content }); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 let parsed; | ||
|  |                 try { | ||
|  |                     parsed = (0, RunnableFunction_1.isRunnableFunctionWithParse)(fn) ? await fn.parse(args) : args; | ||
|  |                 } | ||
|  |                 catch (error) { | ||
|  |                     const content = error instanceof Error ? error.message : String(error); | ||
|  |                     this._addMessage({ role, tool_call_id, content }); | ||
|  |                     continue; | ||
|  |                 } | ||
|  |                 // @ts-expect-error it can't rule out `never` type.
 | ||
|  |                 const rawContent = await fn.function(parsed, this); | ||
|  |                 const content = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_stringifyFunctionCallResult).call(this, rawContent); | ||
|  |                 this._addMessage({ role, tool_call_id, content }); | ||
|  |                 if (singleFunctionToCall) { | ||
|  |                     return; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  |         return; | ||
|  |     } | ||
|  | } | ||
|  | exports.AbstractChatCompletionRunner = AbstractChatCompletionRunner; | ||
|  | _AbstractChatCompletionRunner_instances = new WeakSet(), _AbstractChatCompletionRunner_getFinalContent = function _AbstractChatCompletionRunner_getFinalContent() { | ||
|  |     return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this).content ?? null; | ||
|  | }, _AbstractChatCompletionRunner_getFinalMessage = function _AbstractChatCompletionRunner_getFinalMessage() { | ||
|  |     let i = this.messages.length; | ||
|  |     while (i-- > 0) { | ||
|  |         const message = this.messages[i]; | ||
|  |         if ((0, chatCompletionUtils_1.isAssistantMessage)(message)) { | ||
|  |             // TODO: support audio here
 | ||
|  |             const ret = { | ||
|  |                 ...message, | ||
|  |                 content: message.content ?? null, | ||
|  |                 refusal: message.refusal ?? null, | ||
|  |             }; | ||
|  |             return ret; | ||
|  |         } | ||
|  |     } | ||
|  |     throw new error_1.OpenAIError('stream ended without producing a ChatCompletionMessage with role=assistant'); | ||
|  | }, _AbstractChatCompletionRunner_getFinalFunctionToolCall = function _AbstractChatCompletionRunner_getFinalFunctionToolCall() { | ||
|  |     for (let i = this.messages.length - 1; i >= 0; i--) { | ||
|  |         const message = this.messages[i]; | ||
|  |         if ((0, chatCompletionUtils_1.isAssistantMessage)(message) && message?.tool_calls?.length) { | ||
|  |             return message.tool_calls.filter((x) => x.type === 'function').at(-1)?.function; | ||
|  |         } | ||
|  |     } | ||
|  |     return; | ||
|  | }, _AbstractChatCompletionRunner_getFinalFunctionToolCallResult = function _AbstractChatCompletionRunner_getFinalFunctionToolCallResult() { | ||
|  |     for (let i = this.messages.length - 1; i >= 0; i--) { | ||
|  |         const message = this.messages[i]; | ||
|  |         if ((0, chatCompletionUtils_1.isToolMessage)(message) && | ||
|  |             message.content != null && | ||
|  |             typeof message.content === 'string' && | ||
|  |             this.messages.some((x) => x.role === 'assistant' && | ||
|  |                 x.tool_calls?.some((y) => y.type === 'function' && y.id === message.tool_call_id))) { | ||
|  |             return message.content; | ||
|  |         } | ||
|  |     } | ||
|  |     return; | ||
|  | }, _AbstractChatCompletionRunner_calculateTotalUsage = function _AbstractChatCompletionRunner_calculateTotalUsage() { | ||
|  |     const total = { | ||
|  |         completion_tokens: 0, | ||
|  |         prompt_tokens: 0, | ||
|  |         total_tokens: 0, | ||
|  |     }; | ||
|  |     for (const { usage } of this._chatCompletions) { | ||
|  |         if (usage) { | ||
|  |             total.completion_tokens += usage.completion_tokens; | ||
|  |             total.prompt_tokens += usage.prompt_tokens; | ||
|  |             total.total_tokens += usage.total_tokens; | ||
|  |         } | ||
|  |     } | ||
|  |     return total; | ||
|  | }, _AbstractChatCompletionRunner_validateParams = function _AbstractChatCompletionRunner_validateParams(params) { | ||
|  |     if (params.n != null && params.n > 1) { | ||
|  |         throw new error_1.OpenAIError('ChatCompletion convenience helpers only support n=1 at this time. To use n>1, please use chat.completions.create() directly.'); | ||
|  |     } | ||
|  | }, _AbstractChatCompletionRunner_stringifyFunctionCallResult = function _AbstractChatCompletionRunner_stringifyFunctionCallResult(rawContent) { | ||
|  |     return (typeof rawContent === 'string' ? rawContent | ||
|  |         : rawContent === undefined ? 'undefined' | ||
|  |             : JSON.stringify(rawContent)); | ||
|  | }; | ||
|  | //# sourceMappingURL=AbstractChatCompletionRunner.js.map
 |