You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
3.6 KiB

11 months ago
  1. const OpenAI = require("openai");
  2. const Provider = require("./ai-provider.js");
  3. const InheritMultiple = require("./helpers/classes.js");
  4. const UnTooled = require("./helpers/untooled.js");
  5. const { toValidNumber } = require("../../../http/index.js");
  6. /**
  7. * The agent provider for the Generic OpenAI provider.
  8. * Since we cannot promise the generic provider even supports tool calling
  9. * which is nearly 100% likely it does not, we can just wrap it in untooled
  10. * which often is far better anyway.
  11. */
  12. class GenericOpenAiProvider extends InheritMultiple([Provider, UnTooled]) {
  13. model;
  14. constructor(config = {}) {
  15. super();
  16. const { model = "gpt-3.5-turbo" } = config;
  17. const client = new OpenAI({
  18. baseURL: process.env.GENERIC_OPEN_AI_BASE_PATH,
  19. apiKey: process.env.GENERIC_OPEN_AI_API_KEY ?? null,
  20. maxRetries: 3,
  21. });
  22. this._client = client;
  23. this.model = model;
  24. this.verbose = true;
  25. this.maxTokens = process.env.GENERIC_OPEN_AI_MAX_TOKENS
  26. ? toValidNumber(process.env.GENERIC_OPEN_AI_MAX_TOKENS, 1024)
  27. : 1024;
  28. }
  29. get client() {
  30. return this._client;
  31. }
  32. async #handleFunctionCallChat({ messages = [] }) {
  33. return await this.client.chat.completions
  34. .create({
  35. model: this.model,
  36. temperature: 0,
  37. messages,
  38. max_tokens: this.maxTokens,
  39. })
  40. .then((result) => {
  41. if (!result.hasOwnProperty("choices"))
  42. throw new Error("Generic OpenAI chat: No results!");
  43. if (result.choices.length === 0)
  44. throw new Error("Generic OpenAI chat: No results length!");
  45. return result.choices[0].message.content;
  46. })
  47. .catch((_) => {
  48. return null;
  49. });
  50. }
  51. /**
  52. * Create a completion based on the received messages.
  53. *
  54. * @param messages A list of messages to send to the API.
  55. * @param functions
  56. * @returns The completion.
  57. */
  58. async complete(messages, functions = null) {
  59. try {
  60. let completion;
  61. if (functions.length > 0) {
  62. const { toolCall, text } = await this.functionCall(
  63. messages,
  64. functions,
  65. this.#handleFunctionCallChat.bind(this)
  66. );
  67. if (toolCall !== null) {
  68. this.providerLog(`Valid tool call found - running ${toolCall.name}.`);
  69. this.deduplicator.trackRun(toolCall.name, toolCall.arguments);
  70. return {
  71. result: null,
  72. functionCall: {
  73. name: toolCall.name,
  74. arguments: toolCall.arguments,
  75. },
  76. cost: 0,
  77. };
  78. }
  79. completion = { content: text };
  80. }
  81. if (!completion?.content) {
  82. this.providerLog(
  83. "Will assume chat completion without tool call inputs."
  84. );
  85. const response = await this.client.chat.completions.create({
  86. model: this.model,
  87. messages: this.cleanMsgs(messages),
  88. });
  89. completion = response.choices[0].message;
  90. }
  91. // The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
  92. // from calling the exact same function over and over in a loop within a single chat exchange
  93. // _but_ we should enable it to call previously used tools in a new chat interaction.
  94. this.deduplicator.reset("runs");
  95. return {
  96. result: completion.content,
  97. cost: 0,
  98. };
  99. } catch (error) {
  100. throw error;
  101. }
  102. }
  103. /**
  104. * Get the cost of the completion.
  105. *
  106. * @param _usage The completion to get the cost for.
  107. * @returns The cost of the completion.
  108. */
  109. getCost(_usage) {
  110. return 0;
  111. }
  112. }
  113. module.exports = GenericOpenAiProvider;