parent
c9d8b8716b
commit
f5cedb2460
|
|
@ -1,9 +1,10 @@
|
||||||
import { load } from 'js-yaml'
|
import { load } from 'js-yaml'
|
||||||
import { BaseLanguageModel } from '@langchain/core/language_models/base'
|
|
||||||
import { OpenApiToolkit } from 'langchain/agents'
|
|
||||||
import { JsonSpec, JsonObject } from './core'
|
|
||||||
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
|
||||||
import { getCredentialData, getCredentialParam, getFileFromStorage } from '../../../src'
|
import { getFileFromStorage, getVars, IDatabaseEntity, IVariable } from '../../../src'
|
||||||
|
import $RefParser from '@apidevtools/json-schema-ref-parser'
|
||||||
|
import { z, ZodSchema, ZodTypeAny } from 'zod'
|
||||||
|
import { defaultCode, DynamicStructuredTool, howToUseCode } from './core'
|
||||||
|
import { DataSource } from 'typeorm'
|
||||||
|
|
||||||
class OpenAPIToolkit_Tools implements INode {
|
class OpenAPIToolkit_Tools implements INode {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -20,69 +21,265 @@ class OpenAPIToolkit_Tools implements INode {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.label = 'OpenAPI Toolkit'
|
this.label = 'OpenAPI Toolkit'
|
||||||
this.name = 'openAPIToolkit'
|
this.name = 'openAPIToolkit'
|
||||||
this.version = 1.0
|
this.version = 2.0
|
||||||
this.type = 'OpenAPIToolkit'
|
this.type = 'OpenAPIToolkit'
|
||||||
this.icon = 'openapi.svg'
|
this.icon = 'openapi.svg'
|
||||||
this.category = 'Tools'
|
this.category = 'Tools'
|
||||||
this.description = 'Load OpenAPI specification'
|
this.description = 'Load OpenAPI specification, and converts each API endpoint to a tool'
|
||||||
this.credential = {
|
|
||||||
label: 'Connect Credential',
|
|
||||||
name: 'credential',
|
|
||||||
type: 'credential',
|
|
||||||
description: 'Only needed if the YAML OpenAPI Spec requires authentication',
|
|
||||||
optional: true,
|
|
||||||
credentialNames: ['openAPIAuth']
|
|
||||||
}
|
|
||||||
this.inputs = [
|
this.inputs = [
|
||||||
{
|
|
||||||
label: 'Language Model',
|
|
||||||
name: 'model',
|
|
||||||
type: 'BaseLanguageModel'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'YAML File',
|
label: 'YAML File',
|
||||||
name: 'yamlFile',
|
name: 'yamlFile',
|
||||||
type: 'file',
|
type: 'file',
|
||||||
fileType: '.yaml'
|
fileType: '.yaml'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Return Direct',
|
||||||
|
name: 'returnDirect',
|
||||||
|
description: 'Return the output of the tool directly to the user',
|
||||||
|
type: 'boolean',
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Headers',
|
||||||
|
name: 'headers',
|
||||||
|
type: 'json',
|
||||||
|
description: 'Request headers to be sent with the API request. For example, {"Authorization": "Bearer token"}',
|
||||||
|
additionalParams: true,
|
||||||
|
optional: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Custom Code',
|
||||||
|
name: 'customCode',
|
||||||
|
type: 'code',
|
||||||
|
hint: {
|
||||||
|
label: 'How to use',
|
||||||
|
value: howToUseCode
|
||||||
|
},
|
||||||
|
codeExample: defaultCode,
|
||||||
|
description: `Custom code to return the output of the tool. The code should be a function that takes in the input and returns a string`,
|
||||||
|
hideCodeExecute: true,
|
||||||
|
default: defaultCode,
|
||||||
|
additionalParams: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
this.baseClasses = [this.type, 'Tool']
|
this.baseClasses = [this.type, 'Tool']
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||||
const model = nodeData.inputs?.model as BaseLanguageModel
|
const toolReturnDirect = nodeData.inputs?.returnDirect as boolean
|
||||||
const yamlFileBase64 = nodeData.inputs?.yamlFile as string
|
const yamlFileBase64 = nodeData.inputs?.yamlFile as string
|
||||||
|
const customCode = nodeData.inputs?.customCode as string
|
||||||
|
const _headers = nodeData.inputs?.headers as string
|
||||||
|
|
||||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
const headers = typeof _headers === 'object' ? _headers : _headers ? JSON.parse(_headers) : {}
|
||||||
const openAPIToken = getCredentialParam('openAPIToken', credentialData, nodeData)
|
|
||||||
|
|
||||||
let data: JsonObject
|
let data
|
||||||
if (yamlFileBase64.startsWith('FILE-STORAGE::')) {
|
if (yamlFileBase64.startsWith('FILE-STORAGE::')) {
|
||||||
const file = yamlFileBase64.replace('FILE-STORAGE::', '')
|
const file = yamlFileBase64.replace('FILE-STORAGE::', '')
|
||||||
const chatflowid = options.chatflowid
|
const chatflowid = options.chatflowid
|
||||||
const fileData = await getFileFromStorage(file, chatflowid)
|
const fileData = await getFileFromStorage(file, chatflowid)
|
||||||
const utf8String = fileData.toString('utf-8')
|
const utf8String = fileData.toString('utf-8')
|
||||||
|
|
||||||
data = load(utf8String) as JsonObject
|
data = load(utf8String)
|
||||||
} else {
|
} else {
|
||||||
const splitDataURI = yamlFileBase64.split(',')
|
const splitDataURI = yamlFileBase64.split(',')
|
||||||
splitDataURI.pop()
|
splitDataURI.pop()
|
||||||
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
const bf = Buffer.from(splitDataURI.pop() || '', 'base64')
|
||||||
const utf8String = bf.toString('utf-8')
|
const utf8String = bf.toString('utf-8')
|
||||||
data = load(utf8String) as JsonObject
|
data = load(utf8String)
|
||||||
}
|
}
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new Error('Failed to load OpenAPI spec')
|
throw new Error('Failed to load OpenAPI spec')
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers: ICommonObject = {
|
const _data: any = await $RefParser.dereference(data)
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
if (openAPIToken) headers.Authorization = `Bearer ${openAPIToken}`
|
|
||||||
const toolkit = new OpenApiToolkit(new JsonSpec(data), model, headers)
|
|
||||||
|
|
||||||
return toolkit.tools
|
const baseUrl = _data.servers[0]?.url
|
||||||
|
if (!baseUrl) {
|
||||||
|
throw new Error('OpenAPI spec does not contain a server URL')
|
||||||
|
}
|
||||||
|
|
||||||
|
const appDataSource = options.appDataSource as DataSource
|
||||||
|
const databaseEntities = options.databaseEntities as IDatabaseEntity
|
||||||
|
const variables = await getVars(appDataSource, databaseEntities, nodeData)
|
||||||
|
|
||||||
|
const flow = { chatflowId: options.chatflowid }
|
||||||
|
|
||||||
|
const tools = getTools(_data.paths, baseUrl, headers, variables, flow, toolReturnDirect, customCode)
|
||||||
|
return tools
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const jsonSchemaToZodSchema = (schema: any, requiredList: string[], keyName: string): ZodSchema<any> => {
|
||||||
|
if (schema.properties) {
|
||||||
|
// Handle object types by recursively processing properties
|
||||||
|
const zodShape: Record<string, ZodTypeAny> = {}
|
||||||
|
for (const key in schema.properties) {
|
||||||
|
zodShape[key] = jsonSchemaToZodSchema(schema.properties[key], requiredList, key)
|
||||||
|
}
|
||||||
|
return z.object(zodShape)
|
||||||
|
} else if (schema.oneOf) {
|
||||||
|
// Handle oneOf by mapping each option to a Zod schema
|
||||||
|
const zodSchemas = schema.oneOf.map((subSchema: any) => jsonSchemaToZodSchema(subSchema, requiredList, keyName))
|
||||||
|
return z.union(zodSchemas)
|
||||||
|
} else if (schema.enum) {
|
||||||
|
// Handle enum types
|
||||||
|
return requiredList.includes(keyName)
|
||||||
|
? z.enum(schema.enum).describe(schema?.description ?? keyName)
|
||||||
|
: z
|
||||||
|
.enum(schema.enum)
|
||||||
|
.describe(schema?.description ?? keyName)
|
||||||
|
.optional()
|
||||||
|
} else if (schema.type === 'string') {
|
||||||
|
return requiredList.includes(keyName)
|
||||||
|
? z.string({ required_error: `${keyName} required` }).describe(schema?.description ?? keyName)
|
||||||
|
: z
|
||||||
|
.string()
|
||||||
|
.describe(schema?.description ?? keyName)
|
||||||
|
.optional()
|
||||||
|
} else if (schema.type === 'array') {
|
||||||
|
return z.array(jsonSchemaToZodSchema(schema.items, requiredList, keyName))
|
||||||
|
} else if (schema.type === 'boolean') {
|
||||||
|
return requiredList.includes(keyName)
|
||||||
|
? z.number({ required_error: `${keyName} required` }).describe(schema?.description ?? keyName)
|
||||||
|
: z
|
||||||
|
.number()
|
||||||
|
.describe(schema?.description ?? keyName)
|
||||||
|
.optional()
|
||||||
|
} else if (schema.type === 'number') {
|
||||||
|
return requiredList.includes(keyName)
|
||||||
|
? z.boolean({ required_error: `${keyName} required` }).describe(schema?.description ?? keyName)
|
||||||
|
: z
|
||||||
|
.boolean()
|
||||||
|
.describe(schema?.description ?? keyName)
|
||||||
|
.optional()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to unknown type if unrecognized
|
||||||
|
return z.unknown()
|
||||||
|
}
|
||||||
|
|
||||||
|
const extractParameters = (param: ICommonObject, paramZodObj: ICommonObject) => {
|
||||||
|
const paramSchema = param.schema
|
||||||
|
const paramName = param.name
|
||||||
|
const paramDesc = param.description || param.name
|
||||||
|
|
||||||
|
if (paramSchema.type === 'string') {
|
||||||
|
if (param.required) {
|
||||||
|
paramZodObj[paramName] = z.string({ required_error: `${paramName} required` }).describe(paramDesc)
|
||||||
|
} else {
|
||||||
|
paramZodObj[paramName] = z.string().describe(paramDesc).optional()
|
||||||
|
}
|
||||||
|
} else if (paramSchema.type === 'number') {
|
||||||
|
if (param.required) {
|
||||||
|
paramZodObj[paramName] = z.number({ required_error: `${paramName} required` }).describe(paramDesc)
|
||||||
|
} else {
|
||||||
|
paramZodObj[paramName] = z.number().describe(paramDesc).optional()
|
||||||
|
}
|
||||||
|
} else if (paramSchema.type === 'boolean') {
|
||||||
|
if (param.required) {
|
||||||
|
paramZodObj[paramName] = z.boolean({ required_error: `${paramName} required` }).describe(paramDesc)
|
||||||
|
} else {
|
||||||
|
paramZodObj[paramName] = z.boolean().describe(paramDesc).optional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paramZodObj
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTools = (
|
||||||
|
paths: any[],
|
||||||
|
baseUrl: string,
|
||||||
|
headers: ICommonObject,
|
||||||
|
variables: IVariable[],
|
||||||
|
flow: ICommonObject,
|
||||||
|
returnDirect: boolean,
|
||||||
|
customCode?: string
|
||||||
|
) => {
|
||||||
|
const tools = []
|
||||||
|
for (const path in paths) {
|
||||||
|
// example of path: "/engines"
|
||||||
|
const methods = paths[path]
|
||||||
|
for (const method in methods) {
|
||||||
|
// example of method: "get"
|
||||||
|
const spec = methods[method]
|
||||||
|
const toolName = spec.operationId
|
||||||
|
const toolDesc = spec.description || spec.summary || toolName
|
||||||
|
|
||||||
|
let zodObj: ICommonObject = {}
|
||||||
|
if (spec.parameters) {
|
||||||
|
// Get parameters with in = path
|
||||||
|
let paramZodObjPath: ICommonObject = {}
|
||||||
|
for (const param of spec.parameters.filter((param: any) => param.in === 'path')) {
|
||||||
|
paramZodObjPath = extractParameters(param, paramZodObjPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get parameters with in = query
|
||||||
|
let paramZodObjQuery: ICommonObject = {}
|
||||||
|
for (const param of spec.parameters.filter((param: any) => param.in === 'query')) {
|
||||||
|
paramZodObjQuery = extractParameters(param, paramZodObjQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine path and query parameters
|
||||||
|
zodObj = {
|
||||||
|
...zodObj,
|
||||||
|
PathParameters: z.object(paramZodObjPath),
|
||||||
|
QueryParameters: z.object(paramZodObjQuery)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.requestBody) {
|
||||||
|
let content: any = {}
|
||||||
|
if (spec.requestBody.content['application/json']) {
|
||||||
|
content = spec.requestBody.content['application/json']
|
||||||
|
} else if (spec.requestBody.content['application/x-www-form-urlencoded']) {
|
||||||
|
content = spec.requestBody.content['application/x-www-form-urlencoded']
|
||||||
|
} else if (spec.requestBody.content['multipart/form-data']) {
|
||||||
|
content = spec.requestBody.content['multipart/form-data']
|
||||||
|
} else if (spec.requestBody.content['text/plain']) {
|
||||||
|
content = spec.requestBody.content['text/plain']
|
||||||
|
}
|
||||||
|
const requestBodySchema = content.schema
|
||||||
|
if (requestBodySchema) {
|
||||||
|
const requiredList = requestBodySchema.required || []
|
||||||
|
const requestBodyZodObj = jsonSchemaToZodSchema(requestBodySchema, requiredList, 'properties')
|
||||||
|
zodObj = {
|
||||||
|
...zodObj,
|
||||||
|
RequestBody: requestBodyZodObj
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
zodObj = {
|
||||||
|
...zodObj,
|
||||||
|
input: z.string().describe('Query input').optional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spec.parameters && !spec.requestBody) {
|
||||||
|
zodObj = {
|
||||||
|
input: z.string().describe('Query input').optional()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolObj = {
|
||||||
|
name: toolName,
|
||||||
|
description: toolDesc,
|
||||||
|
schema: z.object(zodObj),
|
||||||
|
baseUrl: `${baseUrl}${path}`,
|
||||||
|
method: method,
|
||||||
|
headers,
|
||||||
|
customCode
|
||||||
|
}
|
||||||
|
|
||||||
|
const dynamicStructuredTool = new DynamicStructuredTool(toolObj)
|
||||||
|
dynamicStructuredTool.setVariables(variables)
|
||||||
|
dynamicStructuredTool.setFlowObject(flow)
|
||||||
|
dynamicStructuredTool.returnDirect = returnDirect
|
||||||
|
tools.push(dynamicStructuredTool)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tools
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { nodeClass: OpenAPIToolkit_Tools }
|
module.exports = { nodeClass: OpenAPIToolkit_Tools }
|
||||||
|
|
|
||||||
|
|
@ -1,140 +1,256 @@
|
||||||
import jsonpointer from 'jsonpointer'
|
import { z } from 'zod'
|
||||||
import { Serializable } from '@langchain/core/load/serializable'
|
import { RequestInit } from 'node-fetch'
|
||||||
import { Tool, ToolParams } from '@langchain/core/tools'
|
import { NodeVM } from '@flowiseai/nodevm'
|
||||||
|
import { RunnableConfig } from '@langchain/core/runnables'
|
||||||
|
import { StructuredTool, ToolParams } from '@langchain/core/tools'
|
||||||
|
import { CallbackManagerForToolRun, Callbacks, CallbackManager, parseCallbackConfigArg } from '@langchain/core/callbacks/manager'
|
||||||
|
import { availableDependencies, defaultAllowBuiltInDep, prepareSandboxVars } from '../../../src/utils'
|
||||||
|
import { ICommonObject } from '../../../src/Interface'
|
||||||
|
|
||||||
export type Json = string | number | boolean | null | { [key: string]: Json } | Json[]
|
interface HttpRequestObject {
|
||||||
|
PathParameters?: Record<string, any>
|
||||||
|
QueryParameters?: Record<string, any>
|
||||||
|
RequestBody?: Record<string, any>
|
||||||
|
}
|
||||||
|
|
||||||
export type JsonObject = { [key: string]: Json }
|
export const defaultCode = `const fetch = require('node-fetch');
|
||||||
|
const url = $url;
|
||||||
|
const options = $options;
|
||||||
|
|
||||||
/**
|
try {
|
||||||
* Represents a JSON object in the LangChain framework. Provides methods
|
const response = await fetch(url, options);
|
||||||
* to get keys and values from the JSON object.
|
const resp = await response.json();
|
||||||
*/
|
return JSON.stringify(resp);
|
||||||
export class JsonSpec extends Serializable {
|
} catch (error) {
|
||||||
lc_namespace = ['langchain', 'tools', 'json']
|
console.error(error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
`
|
||||||
|
export const howToUseCode = `- **Libraries:**
|
||||||
|
You can use any libraries imported in Flowise.
|
||||||
|
|
||||||
obj: JsonObject
|
- **Tool Input Arguments:**
|
||||||
|
Tool input arguments are available as the following variables:
|
||||||
|
- \`$PathParameters\`
|
||||||
|
- \`$QueryParameters\`
|
||||||
|
- \`$RequestBody\`
|
||||||
|
|
||||||
maxValueLength = 4000
|
- **HTTP Requests:**
|
||||||
|
By default, you can get the following values for making HTTP requests:
|
||||||
|
- \`$url\`
|
||||||
|
- \`$options\`
|
||||||
|
|
||||||
constructor(obj: JsonObject, max_value_length = 4000) {
|
- **Default Flow Config:**
|
||||||
super(...arguments)
|
You can access the default flow configuration using these variables:
|
||||||
this.obj = obj
|
- \`$flow.sessionId\`
|
||||||
this.maxValueLength = max_value_length
|
- \`$flow.chatId\`
|
||||||
|
- \`$flow.chatflowId\`
|
||||||
|
- \`$flow.input\`
|
||||||
|
- \`$flow.state\`
|
||||||
|
|
||||||
|
- **Custom Variables:**
|
||||||
|
You can get custom variables using the syntax:
|
||||||
|
- \`$vars.<variable-name>\`
|
||||||
|
|
||||||
|
- **Return Value:**
|
||||||
|
The function must return a **string** value at the end.
|
||||||
|
|
||||||
|
\`\`\`js
|
||||||
|
${defaultCode}
|
||||||
|
\`\`\`
|
||||||
|
`
|
||||||
|
|
||||||
|
const getUrl = (baseUrl: string, requestObject: HttpRequestObject) => {
|
||||||
|
let url = baseUrl
|
||||||
|
|
||||||
|
// Add PathParameters to URL if present
|
||||||
|
if (requestObject.PathParameters) {
|
||||||
|
for (const [key, value] of Object.entries(requestObject.PathParameters)) {
|
||||||
|
url = url.replace(`{${key}}`, encodeURIComponent(String(value)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Add QueryParameters to URL if present
|
||||||
* Retrieves all keys at a given path in the JSON object.
|
if (requestObject.QueryParameters) {
|
||||||
* @param input The path to the keys in the JSON object, provided as a string in JSON pointer syntax.
|
const queryParams = new URLSearchParams(requestObject.QueryParameters as Record<string, string>)
|
||||||
* @returns A string containing all keys at the given path, separated by commas.
|
url += `?${queryParams.toString()}`
|
||||||
*/
|
|
||||||
public getKeys(input: string): string {
|
|
||||||
const pointer = jsonpointer.compile(input)
|
|
||||||
const res = pointer.get(this.obj) as Json
|
|
||||||
if (typeof res === 'object' && !Array.isArray(res) && res !== null) {
|
|
||||||
return Object.keys(res)
|
|
||||||
.map((i) => i.replaceAll('~', '~0').replaceAll('/', '~1'))
|
|
||||||
.join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Value at ${input} is not a dictionary, get the value directly instead.`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return url
|
||||||
* Retrieves the value at a given path in the JSON object.
|
}
|
||||||
* @param input The path to the value in the JSON object, provided as a string in JSON pointer syntax.
|
|
||||||
* @returns The value at the given path in the JSON object, as a string. If the value is a large dictionary or exceeds the maximum length, a message is returned instead.
|
|
||||||
*/
|
|
||||||
public getValue(input: string): string {
|
|
||||||
const pointer = jsonpointer.compile(input)
|
|
||||||
const res = pointer.get(this.obj) as Json
|
|
||||||
|
|
||||||
if (res === null || res === undefined) {
|
class ToolInputParsingException extends Error {
|
||||||
throw new Error(`Value at ${input} is null or undefined.`)
|
output?: string
|
||||||
}
|
|
||||||
|
|
||||||
const str = typeof res === 'object' ? JSON.stringify(res) : res.toString()
|
constructor(message: string, output?: string) {
|
||||||
if (typeof res === 'object' && !Array.isArray(res) && str.length > this.maxValueLength) {
|
super(message)
|
||||||
return `Value is a large dictionary, should explore its keys directly.`
|
this.output = output
|
||||||
}
|
|
||||||
|
|
||||||
if (str.length > this.maxValueLength) {
|
|
||||||
return `${str.slice(0, this.maxValueLength)}...`
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface JsonToolFields extends ToolParams {
|
export interface BaseDynamicToolInput extends ToolParams {
|
||||||
jsonSpec: JsonSpec
|
name: string
|
||||||
|
description: string
|
||||||
|
returnDirect?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export interface DynamicStructuredToolInput<
|
||||||
* A tool in the LangChain framework that lists all keys at a given path
|
// eslint-disable-next-line
|
||||||
* in a JSON object.
|
T extends z.ZodObject<any, any, any, any> = z.ZodObject<any, any, any, any>
|
||||||
*/
|
> extends BaseDynamicToolInput {
|
||||||
export class JsonListKeysTool extends Tool {
|
func?: (input: z.infer<T>, runManager?: CallbackManagerForToolRun) => Promise<string>
|
||||||
static lc_name() {
|
schema: T
|
||||||
return 'JsonListKeysTool'
|
baseUrl: string
|
||||||
}
|
method: string
|
||||||
|
headers: ICommonObject
|
||||||
|
customCode?: string
|
||||||
|
}
|
||||||
|
|
||||||
name = 'json_list_keys'
|
export class DynamicStructuredTool<
|
||||||
|
// eslint-disable-next-line
|
||||||
|
T extends z.ZodObject<any, any, any, any> = z.ZodObject<any, any, any, any>
|
||||||
|
> extends StructuredTool {
|
||||||
|
name: string
|
||||||
|
|
||||||
jsonSpec: JsonSpec
|
description: string
|
||||||
|
|
||||||
constructor(jsonSpec: JsonSpec)
|
baseUrl: string
|
||||||
|
|
||||||
constructor(fields: JsonToolFields)
|
method: string
|
||||||
|
|
||||||
constructor(fields: JsonSpec | JsonToolFields) {
|
headers: ICommonObject
|
||||||
if (!('jsonSpec' in fields)) {
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
customCode?: string
|
||||||
fields = { jsonSpec: fields }
|
|
||||||
}
|
func: DynamicStructuredToolInput['func']
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
schema: T
|
||||||
|
private variables: any[]
|
||||||
|
private flowObj: any
|
||||||
|
|
||||||
|
constructor(fields: DynamicStructuredToolInput<T>) {
|
||||||
super(fields)
|
super(fields)
|
||||||
|
this.name = fields.name
|
||||||
this.jsonSpec = fields.jsonSpec
|
this.description = fields.description
|
||||||
|
this.func = fields.func
|
||||||
|
this.returnDirect = fields.returnDirect ?? this.returnDirect
|
||||||
|
this.schema = fields.schema
|
||||||
|
this.baseUrl = fields.baseUrl
|
||||||
|
this.method = fields.method
|
||||||
|
this.headers = fields.headers
|
||||||
|
this.customCode = fields.customCode
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @ignore */
|
async call(
|
||||||
async _call(input: string) {
|
arg: z.output<T>,
|
||||||
try {
|
configArg?: RunnableConfig | Callbacks,
|
||||||
return this.jsonSpec.getKeys(input)
|
tags?: string[],
|
||||||
} catch (error) {
|
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
||||||
return `${error}`
|
): Promise<string> {
|
||||||
|
const config = parseCallbackConfigArg(configArg)
|
||||||
|
if (config.runName === undefined) {
|
||||||
|
config.runName = this.name
|
||||||
}
|
}
|
||||||
}
|
let parsed
|
||||||
|
|
||||||
description = `Can be used to list all keys at a given path.
|
|
||||||
Before calling this you should be SURE that the path to this exists.
|
|
||||||
The input is a text representation of the path to the json as json pointer syntax (e.g. /key1/0/key2).`
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A tool in the LangChain framework that retrieves the value at a given
|
|
||||||
* path in a JSON object.
|
|
||||||
*/
|
|
||||||
export class JsonGetValueTool extends Tool {
|
|
||||||
static lc_name() {
|
|
||||||
return 'JsonGetValueTool'
|
|
||||||
}
|
|
||||||
|
|
||||||
name = 'json_get_value'
|
|
||||||
|
|
||||||
constructor(public jsonSpec: JsonSpec) {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @ignore */
|
|
||||||
async _call(input: string) {
|
|
||||||
try {
|
try {
|
||||||
return this.jsonSpec.getValue(input)
|
parsed = await this.schema.parseAsync(arg)
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
return `${error}`
|
throw new ToolInputParsingException(`Received tool input did not match expected schema`, JSON.stringify(arg))
|
||||||
}
|
}
|
||||||
|
const callbackManager_ = await CallbackManager.configure(
|
||||||
|
config.callbacks,
|
||||||
|
this.callbacks,
|
||||||
|
config.tags || tags,
|
||||||
|
this.tags,
|
||||||
|
config.metadata,
|
||||||
|
this.metadata,
|
||||||
|
{ verbose: this.verbose }
|
||||||
|
)
|
||||||
|
const runManager = await callbackManager_?.handleToolStart(
|
||||||
|
this.toJSON(),
|
||||||
|
typeof parsed === 'string' ? parsed : JSON.stringify(parsed),
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
config.runName
|
||||||
|
)
|
||||||
|
let result
|
||||||
|
try {
|
||||||
|
result = await this._call(parsed, runManager, flowConfig)
|
||||||
|
} catch (e) {
|
||||||
|
await runManager?.handleToolError(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
if (result && typeof result !== 'string') {
|
||||||
|
result = JSON.stringify(result)
|
||||||
|
}
|
||||||
|
await runManager?.handleToolEnd(result)
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
description = `Can be used to see value in string format at a given path.
|
// @ts-ignore
|
||||||
Before calling this you should be SURE that the path to this exists.
|
protected async _call(
|
||||||
The input is a text representation of the path to the json as json pointer syntax (e.g. /key1/0/key2).`
|
arg: z.output<T>,
|
||||||
|
_?: CallbackManagerForToolRun,
|
||||||
|
flowConfig?: { sessionId?: string; chatId?: string; input?: string; state?: ICommonObject }
|
||||||
|
): Promise<string> {
|
||||||
|
let sandbox: any = {}
|
||||||
|
if (typeof arg === 'object' && Object.keys(arg).length) {
|
||||||
|
for (const item in arg) {
|
||||||
|
sandbox[`$${item}`] = arg[item]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sandbox['$vars'] = prepareSandboxVars(this.variables)
|
||||||
|
|
||||||
|
// inject flow properties
|
||||||
|
if (this.flowObj) {
|
||||||
|
sandbox['$flow'] = { ...this.flowObj, ...flowConfig }
|
||||||
|
}
|
||||||
|
|
||||||
|
const callOptions: RequestInit = {
|
||||||
|
method: this.method,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...this.headers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (arg.RequestBody && this.method.toUpperCase() !== 'GET') {
|
||||||
|
callOptions.body = JSON.stringify(arg.RequestBody)
|
||||||
|
}
|
||||||
|
sandbox['$options'] = callOptions
|
||||||
|
|
||||||
|
const completeUrl = getUrl(this.baseUrl, arg)
|
||||||
|
sandbox['$url'] = completeUrl
|
||||||
|
|
||||||
|
const builtinDeps = process.env.TOOL_FUNCTION_BUILTIN_DEP
|
||||||
|
? defaultAllowBuiltInDep.concat(process.env.TOOL_FUNCTION_BUILTIN_DEP.split(','))
|
||||||
|
: defaultAllowBuiltInDep
|
||||||
|
const externalDeps = process.env.TOOL_FUNCTION_EXTERNAL_DEP ? process.env.TOOL_FUNCTION_EXTERNAL_DEP.split(',') : []
|
||||||
|
const deps = availableDependencies.concat(externalDeps)
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
console: 'inherit',
|
||||||
|
sandbox,
|
||||||
|
require: {
|
||||||
|
external: { modules: deps },
|
||||||
|
builtin: builtinDeps
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const vm = new NodeVM(options)
|
||||||
|
const response = await vm.run(`module.exports = async function() {${this.customCode || defaultCode}}()`, __dirname)
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
setVariables(variables: any[]) {
|
||||||
|
this.variables = variables
|
||||||
|
}
|
||||||
|
|
||||||
|
setFlowObject(flow: any) {
|
||||||
|
this.flowObj = flow
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
},
|
},
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@apidevtools/json-schema-ref-parser": "^11.7.0",
|
||||||
"@aws-sdk/client-bedrock-runtime": "3.422.0",
|
"@aws-sdk/client-bedrock-runtime": "3.422.0",
|
||||||
"@aws-sdk/client-dynamodb": "^3.360.0",
|
"@aws-sdk/client-dynamodb": "^3.360.0",
|
||||||
"@aws-sdk/client-s3": "^3.427.0",
|
"@aws-sdk/client-s3": "^3.427.0",
|
||||||
|
|
|
||||||
65089
pnpm-lock.yaml
65089
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue