From aac9160a9fa0212b33dfe67ad33e9849d7626ddc Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 23 May 2023 00:01:16 +0100 Subject: [PATCH] add requests tools --- .../nodes/tools/RequestsGet/RequestsGet.ts | 37 ++++++---- .../nodes/tools/RequestsGet/core.ts | 42 ++++++++++++ .../nodes/tools/RequestsPost/RequestsPost.ts | 51 ++++++++++---- .../nodes/tools/RequestsPost/core.ts | 68 +++++++++++++++++++ .../ui-component/tooltip/TooltipWithParser.js | 17 +++-- packages/ui/src/views/canvas/CanvasNode.js | 11 ++- .../ui/src/views/canvas/NodeInputHandler.js | 2 + 7 files changed, 199 insertions(+), 29 deletions(-) create mode 100644 packages/components/nodes/tools/RequestsGet/core.ts create mode 100644 packages/components/nodes/tools/RequestsPost/core.ts diff --git a/packages/components/nodes/tools/RequestsGet/RequestsGet.ts b/packages/components/nodes/tools/RequestsGet/RequestsGet.ts index 2085187b5..0b7f0ac80 100644 --- a/packages/components/nodes/tools/RequestsGet/RequestsGet.ts +++ b/packages/components/nodes/tools/RequestsGet/RequestsGet.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { RequestsGetTool } from 'langchain/tools' +import { desc, RequestParameters, RequestsGetTool } from './core' class RequestsGet_Tools implements INode { label: string @@ -22,9 +22,22 @@ class RequestsGet_Tools implements INode { this.baseClasses = [this.type, ...getBaseClasses(RequestsGetTool)] this.inputs = [ { - label: 'Max Output Length', - name: 'maxOutputLength', - type: 'number', + label: 'URL', + name: 'url', + type: 'string', + description: + 'Agent will make call to this exact URL. If not specified, agent will try to figure out itself from AIPlugin if provided', + additionalParams: true, + optional: true + }, + { + label: 'Description', + name: 'description', + type: 'string', + rows: 4, + default: desc, + description: 'Acts like a prompt to tell agent when it should use this tool', + additionalParams: true, optional: true }, { @@ -39,18 +52,18 @@ class RequestsGet_Tools implements INode { async init(nodeData: INodeData): Promise { const headers = nodeData.inputs?.headers as string - const maxOutputLength = nodeData.inputs?.maxOutputLength as string - - const obj: any = {} - if (maxOutputLength) { - obj.maxOutputLength = parseInt(maxOutputLength, 10) - } + const url = nodeData.inputs?.url as string + const description = nodeData.inputs?.description as string + const obj: RequestParameters = {} + if (url) obj.url = url + if (description) obj.description = description if (headers) { const parsedHeaders = typeof headers === 'object' ? headers : JSON.parse(headers) - return Object.keys(obj).length ? new RequestsGetTool(parsedHeaders, obj) : new RequestsGetTool(parsedHeaders) + obj.headers = parsedHeaders } - return Object.keys(obj).length ? new RequestsGetTool(undefined, obj) : new RequestsGetTool() + + return new RequestsGetTool(obj) } } diff --git a/packages/components/nodes/tools/RequestsGet/core.ts b/packages/components/nodes/tools/RequestsGet/core.ts new file mode 100644 index 000000000..a8c23c978 --- /dev/null +++ b/packages/components/nodes/tools/RequestsGet/core.ts @@ -0,0 +1,42 @@ +import { Tool } from 'langchain/tools' + +export const desc = `A portal to the internet. Use this when you need to get specific content from a website. +Input should be a url (i.e. https://www.google.com). The output will be the text response of the GET request.` + +export interface Headers { + [key: string]: string +} + +export interface RequestParameters { + headers?: Headers + url?: string + description?: string + maxOutputLength?: number +} + +export class RequestsGetTool extends Tool { + name = 'requests_get' + url = '' + description = desc + maxOutputLength = 2000 + headers = {} + + constructor(args?: RequestParameters) { + super() + this.url = args?.url ?? this.url + this.headers = args?.headers ?? this.headers + this.description = args?.description ?? this.description + this.maxOutputLength = args?.maxOutputLength ?? this.maxOutputLength + } + + /** @ignore */ + async _call(input: string) { + const inputUrl = this.url ?? input + if (process.env.DEBUG === 'true') console.info(`Making GET API call to ${inputUrl}`) + const res = await fetch(inputUrl, { + headers: this.headers + }) + const text = await res.text() + return text.slice(0, this.maxOutputLength) + } +} diff --git a/packages/components/nodes/tools/RequestsPost/RequestsPost.ts b/packages/components/nodes/tools/RequestsPost/RequestsPost.ts index f0535c97e..0e64556fa 100644 --- a/packages/components/nodes/tools/RequestsPost/RequestsPost.ts +++ b/packages/components/nodes/tools/RequestsPost/RequestsPost.ts @@ -1,6 +1,6 @@ import { INode, INodeData, INodeParams } from '../../../src/Interface' import { getBaseClasses } from '../../../src/utils' -import { RequestsPostTool } from 'langchain/tools' +import { RequestParameters, desc, RequestsPostTool } from './core' class RequestsPost_Tools implements INode { label: string @@ -22,9 +22,31 @@ class RequestsPost_Tools implements INode { this.baseClasses = [this.type, ...getBaseClasses(RequestsPostTool)] this.inputs = [ { - label: 'Max Output Length', - name: 'maxOutputLength', - type: 'number', + label: 'URL', + name: 'url', + type: 'string', + description: + 'Agent will make call to this exact URL. If not specified, agent will try to figure out itself from AIPlugin if provided', + additionalParams: true, + optional: true + }, + { + label: 'Body', + name: 'body', + type: 'json', + description: + 'JSON body for the POST request. If not specified, agent will try to figure out itself from AIPlugin if provided', + additionalParams: true, + optional: true + }, + { + label: 'Description', + name: 'description', + type: 'string', + rows: 4, + default: desc, + description: 'Acts like a prompt to tell agent when it should use this tool', + additionalParams: true, optional: true }, { @@ -39,18 +61,23 @@ class RequestsPost_Tools implements INode { async init(nodeData: INodeData): Promise { const headers = nodeData.inputs?.headers as string - const maxOutputLength = nodeData.inputs?.maxOutputLength as string - - const obj: any = {} - if (maxOutputLength) { - obj.maxOutputLength = parseInt(maxOutputLength, 10) - } + const url = nodeData.inputs?.url as string + const description = nodeData.inputs?.description as string + const body = nodeData.inputs?.body as string + const obj: RequestParameters = {} + if (url) obj.url = url + if (description) obj.description = description if (headers) { const parsedHeaders = typeof headers === 'object' ? headers : JSON.parse(headers) - return Object.keys(obj).length ? new RequestsPostTool(parsedHeaders, obj) : new RequestsPostTool(parsedHeaders) + obj.headers = parsedHeaders } - return Object.keys(obj).length ? new RequestsPostTool(undefined, obj) : new RequestsPostTool() + if (body) { + const parsedBody = typeof body === 'object' ? body : JSON.parse(body) + obj.body = parsedBody + } + + return new RequestsPostTool(obj) } } diff --git a/packages/components/nodes/tools/RequestsPost/core.ts b/packages/components/nodes/tools/RequestsPost/core.ts new file mode 100644 index 000000000..4056f60b9 --- /dev/null +++ b/packages/components/nodes/tools/RequestsPost/core.ts @@ -0,0 +1,68 @@ +import { Tool } from 'langchain/tools' + +export const desc = `Use this when you want to POST to a website. +Input should be a json string with two keys: "url" and "data". +The value of "url" should be a string, and the value of "data" should be a dictionary of +key-value pairs you want to POST to the url as a JSON body. +Be careful to always use double quotes for strings in the json string +The output will be the text response of the POST request.` + +export interface Headers { + [key: string]: string +} + +export interface Body { + [key: string]: any +} + +export interface RequestParameters { + headers?: Headers + body?: Body + url?: string + description?: string + maxOutputLength?: number +} + +export class RequestsPostTool extends Tool { + name = 'requests_post' + url = '' + description = desc + maxOutputLength = Infinity + headers = {} + body = {} + + constructor(args?: RequestParameters) { + super() + this.url = args?.url ?? this.url + this.headers = args?.headers ?? this.headers + this.body = args?.body ?? this.body + this.description = args?.description ?? this.description + this.maxOutputLength = args?.maxOutputLength ?? this.maxOutputLength + } + + /** @ignore */ + async _call(input: string) { + try { + let inputUrl = '' + let inputBody = {} + if (Object.keys(this.body).length || this.url) { + if (this.url) inputUrl = this.url + if (Object.keys(this.body).length) inputBody = this.body + } else { + const { url, data } = JSON.parse(input) + inputUrl = url + inputBody = data + } + if (process.env.DEBUG === 'true') console.info(`Making POST API call to ${inputUrl} with body ${JSON.stringify(inputBody)}`) + const res = await fetch(inputUrl, { + method: 'POST', + headers: this.headers, + body: JSON.stringify(inputBody) + }) + const text = await res.text() + return text.slice(0, this.maxOutputLength) + } catch (error) { + return `${error}` + } + } +} diff --git a/packages/ui/src/ui-component/tooltip/TooltipWithParser.js b/packages/ui/src/ui-component/tooltip/TooltipWithParser.js index a47083779..7362b55c0 100644 --- a/packages/ui/src/ui-component/tooltip/TooltipWithParser.js +++ b/packages/ui/src/ui-component/tooltip/TooltipWithParser.js @@ -4,18 +4,27 @@ import parser from 'html-react-parser' import PropTypes from 'prop-types' import { useSelector } from 'react-redux' -export const TooltipWithParser = ({ title }) => { +export const TooltipWithParser = ({ title, style }) => { const customization = useSelector((state) => state.customization) return ( - - + + ) } TooltipWithParser.propTypes = { - title: PropTypes.node + title: PropTypes.node, + style: PropTypes.any } diff --git a/packages/ui/src/views/canvas/CanvasNode.js b/packages/ui/src/views/canvas/CanvasNode.js index c59b288d3..9263d4b6a 100644 --- a/packages/ui/src/views/canvas/CanvasNode.js +++ b/packages/ui/src/views/canvas/CanvasNode.js @@ -134,7 +134,16 @@ const CanvasNode = ({ data }) => { ))} {data.inputParams.find((param) => param.additionalParams) && ( -
+
param.additionalParams).length === + data.inputParams.length + data.inputAnchors.length + ? 20 + : 0 + }} + > diff --git a/packages/ui/src/views/canvas/NodeInputHandler.js b/packages/ui/src/views/canvas/NodeInputHandler.js index 99b0aed40..1dc656e8f 100644 --- a/packages/ui/src/views/canvas/NodeInputHandler.js +++ b/packages/ui/src/views/canvas/NodeInputHandler.js @@ -17,6 +17,7 @@ import { SwitchInput } from 'ui-component/switch/Switch' import { flowContext } from 'store/context/ReactFlowContext' import { isValidConnection, getAvailableNodesForVariable } from 'utils/genericHelper' import { JsonEditorInput } from 'ui-component/json/JsonEditor' +import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser' const CustomWidthTooltip = styled(({ className, ...props }) => )({ [`& .${tooltipClasses.tooltip}`]: { @@ -123,6 +124,7 @@ const NodeInputHandler = ({ inputAnchor, inputParam, data, disabled = false, isA {inputParam.label} {!inputParam.optional &&  *} + {inputParam.description && }
{inputParam.type === 'string' && inputParam.rows && (