AbstractChatCompletionRunner.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. "use strict";
  2. var _AbstractChatCompletionRunner_instances, _AbstractChatCompletionRunner_getFinalContent, _AbstractChatCompletionRunner_getFinalMessage, _AbstractChatCompletionRunner_getFinalFunctionToolCall, _AbstractChatCompletionRunner_getFinalFunctionToolCallResult, _AbstractChatCompletionRunner_calculateTotalUsage, _AbstractChatCompletionRunner_validateParams, _AbstractChatCompletionRunner_stringifyFunctionCallResult;
  3. Object.defineProperty(exports, "__esModule", { value: true });
  4. exports.AbstractChatCompletionRunner = void 0;
  5. const tslib_1 = require("../internal/tslib.js");
  6. const error_1 = require("../error.js");
  7. const parser_1 = require("../lib/parser.js");
  8. const chatCompletionUtils_1 = require("./chatCompletionUtils.js");
  9. const EventStream_1 = require("./EventStream.js");
  10. const RunnableFunction_1 = require("./RunnableFunction.js");
  11. const DEFAULT_MAX_CHAT_COMPLETIONS = 10;
  12. class AbstractChatCompletionRunner extends EventStream_1.EventStream {
  13. constructor() {
  14. super(...arguments);
  15. _AbstractChatCompletionRunner_instances.add(this);
  16. this._chatCompletions = [];
  17. this.messages = [];
  18. }
  19. _addChatCompletion(chatCompletion) {
  20. this._chatCompletions.push(chatCompletion);
  21. this._emit('chatCompletion', chatCompletion);
  22. const message = chatCompletion.choices[0]?.message;
  23. if (message)
  24. this._addMessage(message);
  25. return chatCompletion;
  26. }
  27. _addMessage(message, emit = true) {
  28. if (!('content' in message))
  29. message.content = null;
  30. this.messages.push(message);
  31. if (emit) {
  32. this._emit('message', message);
  33. if ((0, chatCompletionUtils_1.isToolMessage)(message) && message.content) {
  34. // Note, this assumes that {role: 'tool', content: …} is always the result of a call of tool of type=function.
  35. this._emit('functionToolCallResult', message.content);
  36. }
  37. else if ((0, chatCompletionUtils_1.isAssistantMessage)(message) && message.tool_calls) {
  38. for (const tool_call of message.tool_calls) {
  39. if (tool_call.type === 'function') {
  40. this._emit('functionToolCall', tool_call.function);
  41. }
  42. }
  43. }
  44. }
  45. }
  46. /**
  47. * @returns a promise that resolves with the final ChatCompletion, or rejects
  48. * if an error occurred or the stream ended prematurely without producing a ChatCompletion.
  49. */
  50. async finalChatCompletion() {
  51. await this.done();
  52. const completion = this._chatCompletions[this._chatCompletions.length - 1];
  53. if (!completion)
  54. throw new error_1.OpenAIError('stream ended without producing a ChatCompletion');
  55. return completion;
  56. }
  57. /**
  58. * @returns a promise that resolves with the content of the final ChatCompletionMessage, or rejects
  59. * if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
  60. */
  61. async finalContent() {
  62. await this.done();
  63. return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalContent).call(this);
  64. }
  65. /**
  66. * @returns a promise that resolves with the the final assistant ChatCompletionMessage response,
  67. * or rejects if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
  68. */
  69. async finalMessage() {
  70. await this.done();
  71. return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this);
  72. }
  73. /**
  74. * @returns a promise that resolves with the content of the final FunctionCall, or rejects
  75. * if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
  76. */
  77. async finalFunctionToolCall() {
  78. await this.done();
  79. return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCall).call(this);
  80. }
  81. async finalFunctionToolCallResult() {
  82. await this.done();
  83. return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCallResult).call(this);
  84. }
  85. async totalUsage() {
  86. await this.done();
  87. return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_calculateTotalUsage).call(this);
  88. }
  89. allChatCompletions() {
  90. return [...this._chatCompletions];
  91. }
  92. _emitFinal() {
  93. const completion = this._chatCompletions[this._chatCompletions.length - 1];
  94. if (completion)
  95. this._emit('finalChatCompletion', completion);
  96. const finalMessage = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this);
  97. if (finalMessage)
  98. this._emit('finalMessage', finalMessage);
  99. const finalContent = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalContent).call(this);
  100. if (finalContent)
  101. this._emit('finalContent', finalContent);
  102. const finalFunctionCall = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCall).call(this);
  103. if (finalFunctionCall)
  104. this._emit('finalFunctionToolCall', finalFunctionCall);
  105. const finalFunctionCallResult = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionToolCallResult).call(this);
  106. if (finalFunctionCallResult != null)
  107. this._emit('finalFunctionToolCallResult', finalFunctionCallResult);
  108. if (this._chatCompletions.some((c) => c.usage)) {
  109. this._emit('totalUsage', tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_calculateTotalUsage).call(this));
  110. }
  111. }
  112. async _createChatCompletion(client, params, options) {
  113. const signal = options?.signal;
  114. if (signal) {
  115. if (signal.aborted)
  116. this.controller.abort();
  117. signal.addEventListener('abort', () => this.controller.abort());
  118. }
  119. tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_validateParams).call(this, params);
  120. const chatCompletion = await client.chat.completions.create({ ...params, stream: false }, { ...options, signal: this.controller.signal });
  121. this._connected();
  122. return this._addChatCompletion((0, parser_1.parseChatCompletion)(chatCompletion, params));
  123. }
  124. async _runChatCompletion(client, params, options) {
  125. for (const message of params.messages) {
  126. this._addMessage(message, false);
  127. }
  128. return await this._createChatCompletion(client, params, options);
  129. }
  130. async _runTools(client, params, options) {
  131. const role = 'tool';
  132. const { tool_choice = 'auto', stream, ...restParams } = params;
  133. const singleFunctionToCall = typeof tool_choice !== 'string' && tool_choice.type === 'function' && tool_choice?.function?.name;
  134. const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS } = options || {};
  135. // TODO(someday): clean this logic up
  136. const inputTools = params.tools.map((tool) => {
  137. if ((0, parser_1.isAutoParsableTool)(tool)) {
  138. if (!tool.$callback) {
  139. throw new error_1.OpenAIError('Tool given to `.runTools()` that does not have an associated function');
  140. }
  141. return {
  142. type: 'function',
  143. function: {
  144. function: tool.$callback,
  145. name: tool.function.name,
  146. description: tool.function.description || '',
  147. parameters: tool.function.parameters,
  148. parse: tool.$parseRaw,
  149. strict: true,
  150. },
  151. };
  152. }
  153. return tool;
  154. });
  155. const functionsByName = {};
  156. for (const f of inputTools) {
  157. if (f.type === 'function') {
  158. functionsByName[f.function.name || f.function.function.name] = f.function;
  159. }
  160. }
  161. const tools = 'tools' in params ?
  162. inputTools.map((t) => t.type === 'function' ?
  163. {
  164. type: 'function',
  165. function: {
  166. name: t.function.name || t.function.function.name,
  167. parameters: t.function.parameters,
  168. description: t.function.description,
  169. strict: t.function.strict,
  170. },
  171. }
  172. : t)
  173. : undefined;
  174. for (const message of params.messages) {
  175. this._addMessage(message, false);
  176. }
  177. for (let i = 0; i < maxChatCompletions; ++i) {
  178. const chatCompletion = await this._createChatCompletion(client, {
  179. ...restParams,
  180. tool_choice,
  181. tools,
  182. messages: [...this.messages],
  183. }, options);
  184. const message = chatCompletion.choices[0]?.message;
  185. if (!message) {
  186. throw new error_1.OpenAIError(`missing message in ChatCompletion response`);
  187. }
  188. if (!message.tool_calls?.length) {
  189. return;
  190. }
  191. for (const tool_call of message.tool_calls) {
  192. if (tool_call.type !== 'function')
  193. continue;
  194. const tool_call_id = tool_call.id;
  195. const { name, arguments: args } = tool_call.function;
  196. const fn = functionsByName[name];
  197. if (!fn) {
  198. const content = `Invalid tool_call: ${JSON.stringify(name)}. Available options are: ${Object.keys(functionsByName)
  199. .map((name) => JSON.stringify(name))
  200. .join(', ')}. Please try again`;
  201. this._addMessage({ role, tool_call_id, content });
  202. continue;
  203. }
  204. else if (singleFunctionToCall && singleFunctionToCall !== name) {
  205. const content = `Invalid tool_call: ${JSON.stringify(name)}. ${JSON.stringify(singleFunctionToCall)} requested. Please try again`;
  206. this._addMessage({ role, tool_call_id, content });
  207. continue;
  208. }
  209. let parsed;
  210. try {
  211. parsed = (0, RunnableFunction_1.isRunnableFunctionWithParse)(fn) ? await fn.parse(args) : args;
  212. }
  213. catch (error) {
  214. const content = error instanceof Error ? error.message : String(error);
  215. this._addMessage({ role, tool_call_id, content });
  216. continue;
  217. }
  218. // @ts-expect-error it can't rule out `never` type.
  219. const rawContent = await fn.function(parsed, this);
  220. const content = tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_stringifyFunctionCallResult).call(this, rawContent);
  221. this._addMessage({ role, tool_call_id, content });
  222. if (singleFunctionToCall) {
  223. return;
  224. }
  225. }
  226. }
  227. return;
  228. }
  229. }
  230. exports.AbstractChatCompletionRunner = AbstractChatCompletionRunner;
  231. _AbstractChatCompletionRunner_instances = new WeakSet(), _AbstractChatCompletionRunner_getFinalContent = function _AbstractChatCompletionRunner_getFinalContent() {
  232. return tslib_1.__classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this).content ?? null;
  233. }, _AbstractChatCompletionRunner_getFinalMessage = function _AbstractChatCompletionRunner_getFinalMessage() {
  234. let i = this.messages.length;
  235. while (i-- > 0) {
  236. const message = this.messages[i];
  237. if ((0, chatCompletionUtils_1.isAssistantMessage)(message)) {
  238. // TODO: support audio here
  239. const ret = {
  240. ...message,
  241. content: message.content ?? null,
  242. refusal: message.refusal ?? null,
  243. };
  244. return ret;
  245. }
  246. }
  247. throw new error_1.OpenAIError('stream ended without producing a ChatCompletionMessage with role=assistant');
  248. }, _AbstractChatCompletionRunner_getFinalFunctionToolCall = function _AbstractChatCompletionRunner_getFinalFunctionToolCall() {
  249. for (let i = this.messages.length - 1; i >= 0; i--) {
  250. const message = this.messages[i];
  251. if ((0, chatCompletionUtils_1.isAssistantMessage)(message) && message?.tool_calls?.length) {
  252. return message.tool_calls.filter((x) => x.type === 'function').at(-1)?.function;
  253. }
  254. }
  255. return;
  256. }, _AbstractChatCompletionRunner_getFinalFunctionToolCallResult = function _AbstractChatCompletionRunner_getFinalFunctionToolCallResult() {
  257. for (let i = this.messages.length - 1; i >= 0; i--) {
  258. const message = this.messages[i];
  259. if ((0, chatCompletionUtils_1.isToolMessage)(message) &&
  260. message.content != null &&
  261. typeof message.content === 'string' &&
  262. this.messages.some((x) => x.role === 'assistant' &&
  263. x.tool_calls?.some((y) => y.type === 'function' && y.id === message.tool_call_id))) {
  264. return message.content;
  265. }
  266. }
  267. return;
  268. }, _AbstractChatCompletionRunner_calculateTotalUsage = function _AbstractChatCompletionRunner_calculateTotalUsage() {
  269. const total = {
  270. completion_tokens: 0,
  271. prompt_tokens: 0,
  272. total_tokens: 0,
  273. };
  274. for (const { usage } of this._chatCompletions) {
  275. if (usage) {
  276. total.completion_tokens += usage.completion_tokens;
  277. total.prompt_tokens += usage.prompt_tokens;
  278. total.total_tokens += usage.total_tokens;
  279. }
  280. }
  281. return total;
  282. }, _AbstractChatCompletionRunner_validateParams = function _AbstractChatCompletionRunner_validateParams(params) {
  283. if (params.n != null && params.n > 1) {
  284. 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.');
  285. }
  286. }, _AbstractChatCompletionRunner_stringifyFunctionCallResult = function _AbstractChatCompletionRunner_stringifyFunctionCallResult(rawContent) {
  287. return (typeof rawContent === 'string' ? rawContent
  288. : rawContent === undefined ? 'undefined'
  289. : JSON.stringify(rawContent));
  290. };
  291. //# sourceMappingURL=AbstractChatCompletionRunner.js.map