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.

180 lines
6.5 KiB

11 months ago
  1. const { Document } = require("../../../../models/documents");
  2. const { safeJsonParse } = require("../../../http");
  3. const { summarizeContent } = require("../utils/summarize");
  4. const Provider = require("../providers/ai-provider");
  5. const docSummarizer = {
  6. name: "document-summarizer",
  7. startupConfig: {
  8. params: {},
  9. },
  10. plugin: function () {
  11. return {
  12. name: this.name,
  13. setup(aibitat) {
  14. aibitat.function({
  15. super: aibitat,
  16. name: this.name,
  17. controller: new AbortController(),
  18. description:
  19. "Can get the list of files available to search with descriptions and can select a single file to open and summarize.",
  20. examples: [
  21. {
  22. prompt: "Summarize example.txt",
  23. call: JSON.stringify({
  24. action: "summarize",
  25. document_filename: "example.txt",
  26. }),
  27. },
  28. {
  29. prompt: "What files can you see?",
  30. call: JSON.stringify({ action: "list", document_filename: null }),
  31. },
  32. {
  33. prompt: "Tell me about readme.md",
  34. call: JSON.stringify({
  35. action: "summarize",
  36. document_filename: "readme.md",
  37. }),
  38. },
  39. ],
  40. parameters: {
  41. $schema: "http://json-schema.org/draft-07/schema#",
  42. type: "object",
  43. properties: {
  44. action: {
  45. type: "string",
  46. enum: ["list", "summarize"],
  47. description:
  48. "The action to take. 'list' will return all files available with their filename and descriptions. 'summarize' will open and summarize the file by the a document name.",
  49. },
  50. document_filename: {
  51. type: "string",
  52. "x-nullable": true,
  53. description:
  54. "The file name of the document you want to get the full content of.",
  55. },
  56. },
  57. additionalProperties: false,
  58. },
  59. handler: async function ({ action, document_filename }) {
  60. if (action === "list") return await this.listDocuments();
  61. if (action === "summarize")
  62. return await this.summarizeDoc(document_filename);
  63. return "There is nothing we can do. This function call returns no information.";
  64. },
  65. /**
  66. * List all documents available in a workspace
  67. * @returns List of files and their descriptions if available.
  68. */
  69. listDocuments: async function () {
  70. try {
  71. this.super.introspect(
  72. `${this.caller}: Looking at the available documents.`
  73. );
  74. const documents = await Document.where({
  75. workspaceId: this.super.handlerProps.invocation.workspace_id,
  76. });
  77. if (documents.length === 0)
  78. return "No documents found - nothing can be done. Stop.";
  79. this.super.introspect(
  80. `${this.caller}: Found ${documents.length} documents`
  81. );
  82. const foundDocuments = documents.map((doc) => {
  83. const metadata = safeJsonParse(doc.metadata, {});
  84. return {
  85. document_id: doc.docId,
  86. filename: metadata?.title ?? "unknown.txt",
  87. description: metadata?.description ?? "no description",
  88. };
  89. });
  90. return JSON.stringify(foundDocuments);
  91. } catch (error) {
  92. this.super.handlerProps.log(
  93. `document-summarizer.list raised an error. ${error.message}`
  94. );
  95. return `Let the user know this action was not successful. An error was raised while listing available files. ${error.message}`;
  96. }
  97. },
  98. summarizeDoc: async function (filename) {
  99. try {
  100. const availableDocs = safeJsonParse(
  101. await this.listDocuments(),
  102. []
  103. );
  104. if (!availableDocs.length) {
  105. this.super.handlerProps.log(
  106. `${this.caller}: No available documents to summarize.`
  107. );
  108. return "No documents were found.";
  109. }
  110. const docInfo = availableDocs.find(
  111. (info) => info.filename === filename
  112. );
  113. if (!docInfo) {
  114. this.super.handlerProps.log(
  115. `${this.caller}: No available document by the name "${filename}".`
  116. );
  117. return `No available document by the name "${filename}".`;
  118. }
  119. const document = await Document.content(docInfo.document_id);
  120. this.super.introspect(
  121. `${this.caller}: Grabbing all content for ${
  122. filename ?? "a discovered file."
  123. }`
  124. );
  125. if (!document.content || document.content.length === 0) {
  126. throw new Error(
  127. "This document has no readable content that could be found."
  128. );
  129. }
  130. const { TokenManager } = require("../../../helpers/tiktoken");
  131. if (
  132. new TokenManager(this.super.model).countFromString(
  133. document.content
  134. ) < Provider.contextLimit(this.super.provider, this.super.model)
  135. ) {
  136. return document.content;
  137. }
  138. this.super.introspect(
  139. `${this.caller}: Summarizing ${filename ?? ""}...`
  140. );
  141. this.super.onAbort(() => {
  142. this.super.handlerProps.log(
  143. "Abort was triggered, exiting summarization early."
  144. );
  145. this.controller.abort();
  146. });
  147. return await summarizeContent({
  148. provider: this.super.provider,
  149. model: this.super.model,
  150. controllerSignal: this.controller.signal,
  151. content: document.content,
  152. });
  153. } catch (error) {
  154. this.super.handlerProps.log(
  155. `document-summarizer.summarizeDoc raised an error. ${error.message}`
  156. );
  157. return `Let the user know this action was not successful. An error was raised while summarizing the file. ${error.message}`;
  158. }
  159. },
  160. });
  161. },
  162. };
  163. },
  164. };
  165. module.exports = {
  166. docSummarizer,
  167. };