|
require('dotenv').config(); |
|
const { z } = require('zod'); |
|
const fs = require('fs'); |
|
const yaml = require('js-yaml'); |
|
const path = require('path'); |
|
const { DynamicStructuredTool } = require('langchain/tools'); |
|
const { createOpenAPIChain } = require('langchain/chains'); |
|
const SUFFIX = 'Prioritize using responses for subsequent requests to better fulfill the query.'; |
|
|
|
const AuthBearer = z |
|
.object({ |
|
type: z.string().includes('service_http'), |
|
authorization_type: z.string().includes('bearer'), |
|
verification_tokens: z.object({ |
|
openai: z.string(), |
|
}), |
|
}) |
|
.catch(() => false); |
|
|
|
const AuthDefinition = z |
|
.object({ |
|
type: z.string(), |
|
authorization_type: z.string(), |
|
verification_tokens: z.object({ |
|
openai: z.string(), |
|
}), |
|
}) |
|
.catch(() => false); |
|
|
|
async function readSpecFile(filePath) { |
|
try { |
|
const fileContents = await fs.promises.readFile(filePath, 'utf8'); |
|
if (path.extname(filePath) === '.json') { |
|
return JSON.parse(fileContents); |
|
} |
|
return yaml.load(fileContents); |
|
} catch (e) { |
|
console.error(e); |
|
return false; |
|
} |
|
} |
|
|
|
async function getSpec(url) { |
|
const RegularUrl = z |
|
.string() |
|
.url() |
|
.catch(() => false); |
|
|
|
if (RegularUrl.parse(url) && path.extname(url) === '.json') { |
|
const response = await fetch(url); |
|
return await response.json(); |
|
} |
|
|
|
const ValidSpecPath = z |
|
.string() |
|
.url() |
|
.catch(async () => { |
|
const spec = path.join(__dirname, '..', '.well-known', 'openapi', url); |
|
if (!fs.existsSync(spec)) { |
|
return false; |
|
} |
|
|
|
return await readSpecFile(spec); |
|
}); |
|
|
|
return ValidSpecPath.parse(url); |
|
} |
|
|
|
async function createOpenAPIPlugin({ data, llm, user, message, verbose = false }) { |
|
let spec; |
|
try { |
|
spec = await getSpec(data.api.url, verbose); |
|
} catch (error) { |
|
verbose && console.debug('getSpec error', error); |
|
return null; |
|
} |
|
|
|
if (!spec) { |
|
verbose && console.debug('No spec found'); |
|
return null; |
|
} |
|
|
|
const headers = {}; |
|
const { auth, description_for_model } = data; |
|
if (auth && AuthDefinition.parse(auth)) { |
|
verbose && console.debug('auth detected', auth); |
|
const { openai } = auth.verification_tokens; |
|
if (AuthBearer.parse(auth)) { |
|
headers.authorization = `Bearer ${openai}`; |
|
verbose && console.debug('added auth bearer', headers); |
|
} |
|
} |
|
|
|
return new DynamicStructuredTool({ |
|
name: data.name_for_model, |
|
description: `${data.description_for_human} ${SUFFIX}`, |
|
schema: z.object({ |
|
query: z |
|
.string() |
|
.describe( |
|
'For the query, be specific in a conversational manner. It will be interpreted by a human.', |
|
), |
|
}), |
|
func: async () => { |
|
const chainOptions = { |
|
llm, |
|
verbose, |
|
}; |
|
|
|
if (data.headers && data.headers['librechat_user_id']) { |
|
verbose && console.debug('id detected', headers); |
|
headers[data.headers['librechat_user_id']] = user; |
|
} |
|
|
|
if (Object.keys(headers).length > 0) { |
|
verbose && console.debug('headers detected', headers); |
|
chainOptions.headers = headers; |
|
} |
|
|
|
if (data.params) { |
|
verbose && console.debug('params detected', data.params); |
|
chainOptions.params = data.params; |
|
} |
|
|
|
const chain = await createOpenAPIChain(spec, chainOptions); |
|
const result = await chain.run( |
|
`${message}\n\n||>Instructions: ${description_for_model}\n${SUFFIX}`, |
|
); |
|
console.log('api chain run result', result); |
|
return result; |
|
}, |
|
}); |
|
} |
|
|
|
module.exports = { |
|
getSpec, |
|
readSpecFile, |
|
createOpenAPIPlugin, |
|
}; |
|
|