AbstractChatCompletionRunner.mjs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
  2. if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
  3. if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
  4. return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
  5. };
  6. var _AbstractChatCompletionRunner_instances, _AbstractChatCompletionRunner_getFinalContent, _AbstractChatCompletionRunner_getFinalMessage, _AbstractChatCompletionRunner_getFinalFunctionCall, _AbstractChatCompletionRunner_getFinalFunctionCallResult, _AbstractChatCompletionRunner_calculateTotalUsage, _AbstractChatCompletionRunner_validateParams, _AbstractChatCompletionRunner_stringifyFunctionCallResult;
  7. import { OpenAIError } from "../error.mjs";
  8. import { isRunnableFunctionWithParse, } from "./RunnableFunction.mjs";
  9. import { isAssistantMessage, isFunctionMessage, isToolMessage } from "./chatCompletionUtils.mjs";
  10. import { EventStream } from "./EventStream.mjs";
  11. import { isAutoParsableTool, parseChatCompletion } from "../lib/parser.mjs";
  12. const DEFAULT_MAX_CHAT_COMPLETIONS = 10;
  13. export class AbstractChatCompletionRunner extends EventStream {
  14. constructor() {
  15. super(...arguments);
  16. _AbstractChatCompletionRunner_instances.add(this);
  17. this._chatCompletions = [];
  18. this.messages = [];
  19. }
  20. _addChatCompletion(chatCompletion) {
  21. this._chatCompletions.push(chatCompletion);
  22. this._emit('chatCompletion', chatCompletion);
  23. const message = chatCompletion.choices[0]?.message;
  24. if (message)
  25. this._addMessage(message);
  26. return chatCompletion;
  27. }
  28. _addMessage(message, emit = true) {
  29. if (!('content' in message))
  30. message.content = null;
  31. this.messages.push(message);
  32. if (emit) {
  33. this._emit('message', message);
  34. if ((isFunctionMessage(message) || isToolMessage(message)) && message.content) {
  35. // Note, this assumes that {role: 'tool', content: …} is always the result of a call of tool of type=function.
  36. this._emit('functionCallResult', message.content);
  37. }
  38. else if (isAssistantMessage(message) && message.function_call) {
  39. this._emit('functionCall', message.function_call);
  40. }
  41. else if (isAssistantMessage(message) && message.tool_calls) {
  42. for (const tool_call of message.tool_calls) {
  43. if (tool_call.type === 'function') {
  44. this._emit('functionCall', tool_call.function);
  45. }
  46. }
  47. }
  48. }
  49. }
  50. /**
  51. * @returns a promise that resolves with the final ChatCompletion, or rejects
  52. * if an error occurred or the stream ended prematurely without producing a ChatCompletion.
  53. */
  54. async finalChatCompletion() {
  55. await this.done();
  56. const completion = this._chatCompletions[this._chatCompletions.length - 1];
  57. if (!completion)
  58. throw new OpenAIError('stream ended without producing a ChatCompletion');
  59. return completion;
  60. }
  61. /**
  62. * @returns a promise that resolves with the content of the final ChatCompletionMessage, or rejects
  63. * if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
  64. */
  65. async finalContent() {
  66. await this.done();
  67. return __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalContent).call(this);
  68. }
  69. /**
  70. * @returns a promise that resolves with the the final assistant ChatCompletionMessage response,
  71. * or rejects if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
  72. */
  73. async finalMessage() {
  74. await this.done();
  75. return __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this);
  76. }
  77. /**
  78. * @returns a promise that resolves with the content of the final FunctionCall, or rejects
  79. * if an error occurred or the stream ended prematurely without producing a ChatCompletionMessage.
  80. */
  81. async finalFunctionCall() {
  82. await this.done();
  83. return __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionCall).call(this);
  84. }
  85. async finalFunctionCallResult() {
  86. await this.done();
  87. return __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionCallResult).call(this);
  88. }
  89. async totalUsage() {
  90. await this.done();
  91. return __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_calculateTotalUsage).call(this);
  92. }
  93. allChatCompletions() {
  94. return [...this._chatCompletions];
  95. }
  96. _emitFinal() {
  97. const completion = this._chatCompletions[this._chatCompletions.length - 1];
  98. if (completion)
  99. this._emit('finalChatCompletion', completion);
  100. const finalMessage = __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this);
  101. if (finalMessage)
  102. this._emit('finalMessage', finalMessage);
  103. const finalContent = __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalContent).call(this);
  104. if (finalContent)
  105. this._emit('finalContent', finalContent);
  106. const finalFunctionCall = __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionCall).call(this);
  107. if (finalFunctionCall)
  108. this._emit('finalFunctionCall', finalFunctionCall);
  109. const finalFunctionCallResult = __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalFunctionCallResult).call(this);
  110. if (finalFunctionCallResult != null)
  111. this._emit('finalFunctionCallResult', finalFunctionCallResult);
  112. if (this._chatCompletions.some((c) => c.usage)) {
  113. this._emit('totalUsage', __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_calculateTotalUsage).call(this));
  114. }
  115. }
  116. async _createChatCompletion(client, params, options) {
  117. const signal = options?.signal;
  118. if (signal) {
  119. if (signal.aborted)
  120. this.controller.abort();
  121. signal.addEventListener('abort', () => this.controller.abort());
  122. }
  123. __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_validateParams).call(this, params);
  124. const chatCompletion = await client.chat.completions.create({ ...params, stream: false }, { ...options, signal: this.controller.signal });
  125. this._connected();
  126. return this._addChatCompletion(parseChatCompletion(chatCompletion, params));
  127. }
  128. async _runChatCompletion(client, params, options) {
  129. for (const message of params.messages) {
  130. this._addMessage(message, false);
  131. }
  132. return await this._createChatCompletion(client, params, options);
  133. }
  134. async _runFunctions(client, params, options) {
  135. const role = 'function';
  136. const { function_call = 'auto', stream, ...restParams } = params;
  137. const singleFunctionToCall = typeof function_call !== 'string' && function_call?.name;
  138. const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS } = options || {};
  139. const functionsByName = {};
  140. for (const f of params.functions) {
  141. functionsByName[f.name || f.function.name] = f;
  142. }
  143. const functions = params.functions.map((f) => ({
  144. name: f.name || f.function.name,
  145. parameters: f.parameters,
  146. description: f.description,
  147. }));
  148. for (const message of params.messages) {
  149. this._addMessage(message, false);
  150. }
  151. for (let i = 0; i < maxChatCompletions; ++i) {
  152. const chatCompletion = await this._createChatCompletion(client, {
  153. ...restParams,
  154. function_call,
  155. functions,
  156. messages: [...this.messages],
  157. }, options);
  158. const message = chatCompletion.choices[0]?.message;
  159. if (!message) {
  160. throw new OpenAIError(`missing message in ChatCompletion response`);
  161. }
  162. if (!message.function_call)
  163. return;
  164. const { name, arguments: args } = message.function_call;
  165. const fn = functionsByName[name];
  166. if (!fn) {
  167. const content = `Invalid function_call: ${JSON.stringify(name)}. Available options are: ${functions
  168. .map((f) => JSON.stringify(f.name))
  169. .join(', ')}. Please try again`;
  170. this._addMessage({ role, name, content });
  171. continue;
  172. }
  173. else if (singleFunctionToCall && singleFunctionToCall !== name) {
  174. const content = `Invalid function_call: ${JSON.stringify(name)}. ${JSON.stringify(singleFunctionToCall)} requested. Please try again`;
  175. this._addMessage({ role, name, content });
  176. continue;
  177. }
  178. let parsed;
  179. try {
  180. parsed = isRunnableFunctionWithParse(fn) ? await fn.parse(args) : args;
  181. }
  182. catch (error) {
  183. this._addMessage({
  184. role,
  185. name,
  186. content: error instanceof Error ? error.message : String(error),
  187. });
  188. continue;
  189. }
  190. // @ts-expect-error it can't rule out `never` type.
  191. const rawContent = await fn.function(parsed, this);
  192. const content = __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_stringifyFunctionCallResult).call(this, rawContent);
  193. this._addMessage({ role, name, content });
  194. if (singleFunctionToCall)
  195. return;
  196. }
  197. }
  198. async _runTools(client, params, options) {
  199. const role = 'tool';
  200. const { tool_choice = 'auto', stream, ...restParams } = params;
  201. const singleFunctionToCall = typeof tool_choice !== 'string' && tool_choice?.function?.name;
  202. const { maxChatCompletions = DEFAULT_MAX_CHAT_COMPLETIONS } = options || {};
  203. // TODO(someday): clean this logic up
  204. const inputTools = params.tools.map((tool) => {
  205. if (isAutoParsableTool(tool)) {
  206. if (!tool.$callback) {
  207. throw new OpenAIError('Tool given to `.runTools()` that does not have an associated function');
  208. }
  209. return {
  210. type: 'function',
  211. function: {
  212. function: tool.$callback,
  213. name: tool.function.name,
  214. description: tool.function.description || '',
  215. parameters: tool.function.parameters,
  216. parse: tool.$parseRaw,
  217. strict: true,
  218. },
  219. };
  220. }
  221. return tool;
  222. });
  223. const functionsByName = {};
  224. for (const f of inputTools) {
  225. if (f.type === 'function') {
  226. functionsByName[f.function.name || f.function.function.name] = f.function;
  227. }
  228. }
  229. const tools = 'tools' in params ?
  230. inputTools.map((t) => t.type === 'function' ?
  231. {
  232. type: 'function',
  233. function: {
  234. name: t.function.name || t.function.function.name,
  235. parameters: t.function.parameters,
  236. description: t.function.description,
  237. strict: t.function.strict,
  238. },
  239. }
  240. : t)
  241. : undefined;
  242. for (const message of params.messages) {
  243. this._addMessage(message, false);
  244. }
  245. for (let i = 0; i < maxChatCompletions; ++i) {
  246. const chatCompletion = await this._createChatCompletion(client, {
  247. ...restParams,
  248. tool_choice,
  249. tools,
  250. messages: [...this.messages],
  251. }, options);
  252. const message = chatCompletion.choices[0]?.message;
  253. if (!message) {
  254. throw new OpenAIError(`missing message in ChatCompletion response`);
  255. }
  256. if (!message.tool_calls?.length) {
  257. return;
  258. }
  259. for (const tool_call of message.tool_calls) {
  260. if (tool_call.type !== 'function')
  261. continue;
  262. const tool_call_id = tool_call.id;
  263. const { name, arguments: args } = tool_call.function;
  264. const fn = functionsByName[name];
  265. if (!fn) {
  266. const content = `Invalid tool_call: ${JSON.stringify(name)}. Available options are: ${Object.keys(functionsByName)
  267. .map((name) => JSON.stringify(name))
  268. .join(', ')}. Please try again`;
  269. this._addMessage({ role, tool_call_id, content });
  270. continue;
  271. }
  272. else if (singleFunctionToCall && singleFunctionToCall !== name) {
  273. const content = `Invalid tool_call: ${JSON.stringify(name)}. ${JSON.stringify(singleFunctionToCall)} requested. Please try again`;
  274. this._addMessage({ role, tool_call_id, content });
  275. continue;
  276. }
  277. let parsed;
  278. try {
  279. parsed = isRunnableFunctionWithParse(fn) ? await fn.parse(args) : args;
  280. }
  281. catch (error) {
  282. const content = error instanceof Error ? error.message : String(error);
  283. this._addMessage({ role, tool_call_id, content });
  284. continue;
  285. }
  286. // @ts-expect-error it can't rule out `never` type.
  287. const rawContent = await fn.function(parsed, this);
  288. const content = __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_stringifyFunctionCallResult).call(this, rawContent);
  289. this._addMessage({ role, tool_call_id, content });
  290. if (singleFunctionToCall) {
  291. return;
  292. }
  293. }
  294. }
  295. return;
  296. }
  297. }
  298. _AbstractChatCompletionRunner_instances = new WeakSet(), _AbstractChatCompletionRunner_getFinalContent = function _AbstractChatCompletionRunner_getFinalContent() {
  299. return __classPrivateFieldGet(this, _AbstractChatCompletionRunner_instances, "m", _AbstractChatCompletionRunner_getFinalMessage).call(this).content ?? null;
  300. }, _AbstractChatCompletionRunner_getFinalMessage = function _AbstractChatCompletionRunner_getFinalMessage() {
  301. let i = this.messages.length;
  302. while (i-- > 0) {
  303. const message = this.messages[i];
  304. if (isAssistantMessage(message)) {
  305. const { function_call, ...rest } = message;
  306. // TODO: support audio here
  307. const ret = {
  308. ...rest,
  309. content: message.content ?? null,
  310. refusal: message.refusal ?? null,
  311. };
  312. if (function_call) {
  313. ret.function_call = function_call;
  314. }
  315. return ret;
  316. }
  317. }
  318. throw new OpenAIError('stream ended without producing a ChatCompletionMessage with role=assistant');
  319. }, _AbstractChatCompletionRunner_getFinalFunctionCall = function _AbstractChatCompletionRunner_getFinalFunctionCall() {
  320. for (let i = this.messages.length - 1; i >= 0; i--) {
  321. const message = this.messages[i];
  322. if (isAssistantMessage(message) && message?.function_call) {
  323. return message.function_call;
  324. }
  325. if (isAssistantMessage(message) && message?.tool_calls?.length) {
  326. return message.tool_calls.at(-1)?.function;
  327. }
  328. }
  329. return;
  330. }, _AbstractChatCompletionRunner_getFinalFunctionCallResult = function _AbstractChatCompletionRunner_getFinalFunctionCallResult() {
  331. for (let i = this.messages.length - 1; i >= 0; i--) {
  332. const message = this.messages[i];
  333. if (isFunctionMessage(message) && message.content != null) {
  334. return message.content;
  335. }
  336. if (isToolMessage(message) &&
  337. message.content != null &&
  338. typeof message.content === 'string' &&
  339. this.messages.some((x) => x.role === 'assistant' &&
  340. x.tool_calls?.some((y) => y.type === 'function' && y.id === message.tool_call_id))) {
  341. return message.content;
  342. }
  343. }
  344. return;
  345. }, _AbstractChatCompletionRunner_calculateTotalUsage = function _AbstractChatCompletionRunner_calculateTotalUsage() {
  346. const total = {
  347. completion_tokens: 0,
  348. prompt_tokens: 0,
  349. total_tokens: 0,
  350. };
  351. for (const { usage } of this._chatCompletions) {
  352. if (usage) {
  353. total.completion_tokens += usage.completion_tokens;
  354. total.prompt_tokens += usage.prompt_tokens;
  355. total.total_tokens += usage.total_tokens;
  356. }
  357. }
  358. return total;
  359. }, _AbstractChatCompletionRunner_validateParams = function _AbstractChatCompletionRunner_validateParams(params) {
  360. if (params.n != null && params.n > 1) {
  361. throw new OpenAIError('ChatCompletion convenience helpers only support n=1 at this time. To use n>1, please use chat.completions.create() directly.');
  362. }
  363. }, _AbstractChatCompletionRunner_stringifyFunctionCallResult = function _AbstractChatCompletionRunner_stringifyFunctionCallResult(rawContent) {
  364. return (typeof rawContent === 'string' ? rawContent
  365. : rawContent === undefined ? 'undefined'
  366. : JSON.stringify(rawContent));
  367. };
  368. //# sourceMappingURL=AbstractChatCompletionRunner.mjs.map