| | const { Tool } = require('langchain/tools'); |
| | const yaml = require('js-yaml'); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | function isJson(str) { |
| | try { |
| | JSON.parse(str); |
| | } catch (e) { |
| | return false; |
| | } |
| | return true; |
| | } |
| |
|
| | function convertJsonToYamlIfApplicable(spec) { |
| | if (isJson(spec)) { |
| | const jsonData = JSON.parse(spec); |
| | return yaml.dump(jsonData); |
| | } |
| | return spec; |
| | } |
| |
|
| | function extractShortVersion(openapiSpec) { |
| | openapiSpec = convertJsonToYamlIfApplicable(openapiSpec); |
| | try { |
| | const fullApiSpec = yaml.load(openapiSpec); |
| | const shortApiSpec = { |
| | openapi: fullApiSpec.openapi, |
| | info: fullApiSpec.info, |
| | paths: {}, |
| | }; |
| |
|
| | for (let path in fullApiSpec.paths) { |
| | shortApiSpec.paths[path] = {}; |
| | for (let method in fullApiSpec.paths[path]) { |
| | shortApiSpec.paths[path][method] = { |
| | summary: fullApiSpec.paths[path][method].summary, |
| | operationId: fullApiSpec.paths[path][method].operationId, |
| | parameters: fullApiSpec.paths[path][method].parameters?.map((parameter) => ({ |
| | name: parameter.name, |
| | description: parameter.description, |
| | })), |
| | }; |
| | } |
| | } |
| |
|
| | return yaml.dump(shortApiSpec); |
| | } catch (e) { |
| | console.log(e); |
| | return ''; |
| | } |
| | } |
| | function printOperationDetails(operationId, openapiSpec) { |
| | openapiSpec = convertJsonToYamlIfApplicable(openapiSpec); |
| | let returnText = ''; |
| | try { |
| | let doc = yaml.load(openapiSpec); |
| | let servers = doc.servers; |
| | let paths = doc.paths; |
| | let components = doc.components; |
| |
|
| | for (let path in paths) { |
| | for (let method in paths[path]) { |
| | let operation = paths[path][method]; |
| | if (operation.operationId === operationId) { |
| | returnText += `The API request to do for operationId "${operationId}" is:\n`; |
| | returnText += `Method: ${method.toUpperCase()}\n`; |
| |
|
| | let url = servers[0].url + path; |
| | returnText += `Path: ${url}\n`; |
| |
|
| | returnText += 'Parameters:\n'; |
| | if (operation.parameters) { |
| | for (let param of operation.parameters) { |
| | let required = param.required ? '' : ' (optional),'; |
| | returnText += `- ${param.name} (${param.in},${required} ${param.schema.type}): ${param.description}\n`; |
| | } |
| | } else { |
| | returnText += ' None\n'; |
| | } |
| | returnText += '\n'; |
| |
|
| | let responseSchema = operation.responses['200'].content['application/json'].schema; |
| |
|
| | |
| | if (responseSchema.$ref) { |
| | |
| | let schemaName = responseSchema.$ref.split('/').pop(); |
| | |
| | responseSchema = components.schemas[schemaName]; |
| | } |
| |
|
| | returnText += 'Response schema:\n'; |
| | returnText += '- Type: ' + responseSchema.type + '\n'; |
| | returnText += '- Additional properties:\n'; |
| | returnText += ' - Type: ' + responseSchema.additionalProperties?.type + '\n'; |
| | if (responseSchema.additionalProperties?.properties) { |
| | returnText += ' - Properties:\n'; |
| | for (let prop in responseSchema.additionalProperties.properties) { |
| | returnText += ` - ${prop} (${responseSchema.additionalProperties.properties[prop].type}): Description not provided in OpenAPI spec\n`; |
| | } |
| | } |
| | } |
| | } |
| | } |
| | if (returnText === '') { |
| | returnText += `No operation with operationId "${operationId}" found.`; |
| | } |
| | return returnText; |
| | } catch (e) { |
| | console.log(e); |
| | return ''; |
| | } |
| | } |
| |
|
| | class AIPluginTool extends Tool { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | get name() { |
| | return this._name; |
| | } |
| |
|
| | get description() { |
| | return this._description; |
| | } |
| |
|
| | constructor(params) { |
| | super(); |
| | this._name = params.name; |
| | this._description = params.description; |
| | this.apiSpec = params.apiSpec; |
| | this.openaiSpec = params.openaiSpec; |
| | this.model = params.model; |
| | } |
| |
|
| | async _call(input) { |
| | let date = new Date(); |
| | let fullDate = `Date: ${date.getDate()}/${ |
| | date.getMonth() + 1 |
| | }/${date.getFullYear()}, Time: ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; |
| | const prompt = `${fullDate}\nQuestion: ${input} \n${this.apiSpec}.`; |
| | console.log(prompt); |
| | const gptResponse = await this.model.predict(prompt); |
| | let operationId = gptResponse.match(/operationId: (.*)/)?.[1]; |
| | if (!operationId) { |
| | return 'No operationId found in the response'; |
| | } |
| | if (operationId == 'No API path found to answer the question') { |
| | return 'No API path found to answer the question'; |
| | } |
| |
|
| | let openApiData = printOperationDetails(operationId, this.openaiSpec); |
| |
|
| | return openApiData; |
| | } |
| |
|
| | static async fromPluginUrl(url, model) { |
| | const aiPluginRes = await fetch(url, {}); |
| | if (!aiPluginRes.ok) { |
| | throw new Error(`Failed to fetch plugin from ${url} with status ${aiPluginRes.status}`); |
| | } |
| | const aiPluginJson = await aiPluginRes.json(); |
| | const apiUrlRes = await fetch(aiPluginJson.api.url, {}); |
| | if (!apiUrlRes.ok) { |
| | throw new Error( |
| | `Failed to fetch API spec from ${aiPluginJson.api.url} with status ${apiUrlRes.status}`, |
| | ); |
| | } |
| | const apiUrlJson = await apiUrlRes.text(); |
| | const shortApiSpec = extractShortVersion(apiUrlJson); |
| | return new AIPluginTool({ |
| | name: aiPluginJson.name_for_model.toLowerCase(), |
| | description: `A \`tool\` to learn the API documentation for ${aiPluginJson.name_for_model.toLowerCase()}, after which you can use 'http_request' to make the actual API call. Short description of how to use the API's results: ${ |
| | aiPluginJson.description_for_model |
| | })`, |
| | apiSpec: ` |
| | As an AI, your task is to identify the operationId of the relevant API path based on the condensed OpenAPI specifications provided. |
| | |
| | Please note: |
| | |
| | 1. Do not imagine URLs. Only use the information provided in the condensed OpenAPI specifications. |
| | |
| | 2. Do not guess the operationId. Identify it strictly based on the API paths and their descriptions. |
| | |
| | Your output should only include: |
| | - operationId: The operationId of the relevant API path |
| | |
| | If you cannot find a suitable API path based on the OpenAPI specifications, please answer only "operationId: No API path found to answer the question". |
| | |
| | Now, based on the question above and the condensed OpenAPI specifications given below, identify the operationId: |
| | |
| | \`\`\` |
| | ${shortApiSpec} |
| | \`\`\` |
| | `, |
| | openaiSpec: apiUrlJson, |
| | model: model, |
| | }); |
| | } |
| | } |
| |
|
| | module.exports = AIPluginTool; |
| |
|