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.

118 lines
3.3 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. class DeepSeekProvider extends InheritMultiple([Provider, UnTooled]) {
  7. model;
  8. constructor(config = {}) {
  9. super();
  10. const { model = "deepseek-chat" } = config;
  11. const client = new OpenAI({
  12. baseURL: "https://api.deepseek.com/v1",
  13. apiKey: process.env.DEEPSEEK_API_KEY ?? null,
  14. maxRetries: 3,
  15. });
  16. this._client = client;
  17. this.model = model;
  18. this.verbose = true;
  19. this.maxTokens = process.env.DEEPSEEK_MAX_TOKENS
  20. ? toValidNumber(process.env.DEEPSEEK_MAX_TOKENS, 1024)
  21. : 1024;
  22. }
  23. get client() {
  24. return this._client;
  25. }
  26. async #handleFunctionCallChat({ messages = [] }) {
  27. return await this.client.chat.completions
  28. .create({
  29. model: this.model,
  30. temperature: 0,
  31. messages,
  32. max_tokens: this.maxTokens,
  33. })
  34. .then((result) => {
  35. if (!result.hasOwnProperty("choices"))
  36. throw new Error("DeepSeek chat: No results!");
  37. if (result.choices.length === 0)
  38. throw new Error("DeepSeek chat: No results length!");
  39. return result.choices[0].message.content;
  40. })
  41. .catch((_) => {
  42. return null;
  43. });
  44. }
  45. /**
  46. * Create a completion based on the received messages.
  47. *
  48. * @param messages A list of messages to send to the API.
  49. * @param functions
  50. * @returns The completion.
  51. */
  52. async complete(messages, functions = null) {
  53. try {
  54. let completion;
  55. if (functions.length > 0) {
  56. const { toolCall, text } = await this.functionCall(
  57. messages,
  58. functions,
  59. this.#handleFunctionCallChat.bind(this)
  60. );
  61. if (toolCall !== null) {
  62. this.providerLog(`Valid tool call found - running ${toolCall.name}.`);
  63. this.deduplicator.trackRun(toolCall.name, toolCall.arguments);
  64. return {
  65. result: null,
  66. functionCall: {
  67. name: toolCall.name,
  68. arguments: toolCall.arguments,
  69. },
  70. cost: 0,
  71. };
  72. }
  73. completion = { content: text };
  74. }
  75. if (!completion?.content) {
  76. this.providerLog(
  77. "Will assume chat completion without tool call inputs."
  78. );
  79. const response = await this.client.chat.completions.create({
  80. model: this.model,
  81. messages: this.cleanMsgs(messages),
  82. });
  83. completion = response.choices[0].message;
  84. }
  85. // The UnTooled class inherited Deduplicator is mostly useful to prevent the agent
  86. // from calling the exact same function over and over in a loop within a single chat exchange
  87. // _but_ we should enable it to call previously used tools in a new chat interaction.
  88. this.deduplicator.reset("runs");
  89. return {
  90. result: completion.content,
  91. cost: 0,
  92. };
  93. } catch (error) {
  94. throw error;
  95. }
  96. }
  97. /**
  98. * Get the cost of the completion.
  99. *
  100. * @param _usage The completion to get the cost for.
  101. * @returns The cost of the completion.
  102. */
  103. getCost(_usage) {
  104. return 0;
  105. }
  106. }
  107. module.exports = DeepSeekProvider;