Files
Flowise/packages/components/nodes/chains/ApiChain/GETApiChain.ts
T
Vinod Kiran 26444ac3ae Feature/sse (#3125)
* Base changes for ServerSide Events (instead of socket.io)

* lint fixes

* adding of interface and separate methods for streaming events

* lint

* first draft, handles both internal and external prediction end points.

* lint fixes

* additional internal end point for streaming and associated changes

* return streamresponse as true to build agent flow

* 1) JSON formatting for internal events
2) other fixes

* 1) convert internal event to metadata to maintain consistency with external response

* fix action and metadata streaming

* fix for error when agent flow is aborted

* prevent subflows from streaming and other code cleanup

* prevent streaming from enclosed tools

* add fix for preventing chaintool streaming

* update lock file

* add open when hidden to sse

* Streaming errors

* Streaming errors

* add fix for showing error message

---------

Co-authored-by: Henry <hzj94@hotmail.com>
2024-09-17 08:01:25 +01:00

139 lines
5.6 KiB
TypeScript

import { BaseLanguageModel } from '@langchain/core/language_models/base'
import { PromptTemplate } from '@langchain/core/prompts'
import { APIChain } from 'langchain/chains'
import { getBaseClasses } from '../../../src/utils'
import { ICommonObject, INode, INodeData, INodeParams, IServerSideEventStreamer } from '../../../src/Interface'
import { ConsoleCallbackHandler, CustomChainHandler, additionalCallbacks } from '../../../src/handler'
export const API_URL_RAW_PROMPT_TEMPLATE = `You are given the below API Documentation:
{api_docs}
Using this documentation, generate the full API url to call for answering the user question.
You should build the API url in order to get a response that is as short as possible, while still getting the necessary information to answer the question. Pay attention to deliberately exclude any unnecessary pieces of data in the API call.
Question:{question}
API url:`
export const API_RESPONSE_RAW_PROMPT_TEMPLATE =
'Given this {api_response} response for {api_url}. use the given response to answer this {question}'
class GETApiChain_Chains implements INode {
label: string
name: string
version: number
type: string
icon: string
category: string
baseClasses: string[]
description: string
inputs: INodeParams[]
constructor() {
this.label = 'GET API Chain'
this.name = 'getApiChain'
this.version = 1.0
this.type = 'GETApiChain'
this.icon = 'get.svg'
this.category = 'Chains'
this.description = 'Chain to run queries against GET API'
this.baseClasses = [this.type, ...getBaseClasses(APIChain)]
this.inputs = [
{
label: 'Language Model',
name: 'model',
type: 'BaseLanguageModel'
},
{
label: 'API Documentation',
name: 'apiDocs',
type: 'string',
description:
'Description of how API works. Please refer to more <a target="_blank" href="https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chains/api/open_meteo_docs.py">examples</a>',
rows: 4
},
{
label: 'Headers',
name: 'headers',
type: 'json',
additionalParams: true,
optional: true
},
{
label: 'URL Prompt',
name: 'urlPrompt',
type: 'string',
description: 'Prompt used to tell LLMs how to construct the URL. Must contains {api_docs} and {question}',
default: API_URL_RAW_PROMPT_TEMPLATE,
rows: 4,
additionalParams: true
},
{
label: 'Answer Prompt',
name: 'ansPrompt',
type: 'string',
description:
'Prompt used to tell LLMs how to return the API response. Must contains {api_response}, {api_url}, and {question}',
default: API_RESPONSE_RAW_PROMPT_TEMPLATE,
rows: 4,
additionalParams: true
}
]
}
async init(nodeData: INodeData): Promise<any> {
const model = nodeData.inputs?.model as BaseLanguageModel
const apiDocs = nodeData.inputs?.apiDocs as string
const headers = nodeData.inputs?.headers as string
const urlPrompt = nodeData.inputs?.urlPrompt as string
const ansPrompt = nodeData.inputs?.ansPrompt as string
const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt)
return chain
}
async run(nodeData: INodeData, input: string, options: ICommonObject): Promise<string> {
const model = nodeData.inputs?.model as BaseLanguageModel
const apiDocs = nodeData.inputs?.apiDocs as string
const headers = nodeData.inputs?.headers as string
const urlPrompt = nodeData.inputs?.urlPrompt as string
const ansPrompt = nodeData.inputs?.ansPrompt as string
const chain = await getAPIChain(apiDocs, model, headers, urlPrompt, ansPrompt)
const loggerHandler = new ConsoleCallbackHandler(options.logger)
const callbacks = await additionalCallbacks(nodeData, options)
const shouldStreamResponse = options.shouldStreamResponse
const sseStreamer: IServerSideEventStreamer = options.sseStreamer as IServerSideEventStreamer
const chatId = options.chatId
if (shouldStreamResponse) {
const handler = new CustomChainHandler(sseStreamer, chatId)
const res = await chain.run(input, [loggerHandler, handler, ...callbacks])
return res
} else {
const res = await chain.run(input, [loggerHandler, ...callbacks])
return res
}
}
}
const getAPIChain = async (documents: string, llm: BaseLanguageModel, headers: string, urlPrompt: string, ansPrompt: string) => {
const apiUrlPrompt = new PromptTemplate({
inputVariables: ['api_docs', 'question'],
template: urlPrompt ? urlPrompt : API_URL_RAW_PROMPT_TEMPLATE
})
const apiResponsePrompt = new PromptTemplate({
inputVariables: ['api_docs', 'question', 'api_url', 'api_response'],
template: ansPrompt ? ansPrompt : API_RESPONSE_RAW_PROMPT_TEMPLATE
})
const chain = APIChain.fromLLMAndAPIDocs(llm, documents, {
apiUrlPrompt,
apiResponsePrompt,
verbose: process.env.DEBUG === 'true' ? true : false,
headers: typeof headers === 'object' ? headers : headers ? JSON.parse(headers) : {}
})
return chain
}
module.exports = { nodeClass: GETApiChain_Chains }