Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions packages/components/credentials/RequestyApi.credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { INodeParams, INodeCredential } from '../src/Interface'

class RequestyAPIAuth implements INodeCredential {
label: string
name: string
version: number
description: string
inputs: INodeParams[]

constructor() {
this.label = 'Requesty API Key'
this.name = 'requestyApi'
this.version = 1.0
this.inputs = [
{
label: 'Requesty API Key',
name: 'requestyApiKey',
type: 'password',
description: 'API Key'
}
]
}
Comment on lines +10 to +22

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The description property is declared on the RequestyAPIAuth class but is never initialized in the constructor. This can lead to undefined values at runtime or potential TypeScript issues. It is recommended to initialize it with a brief description of the credential.

    constructor() {
        this.label = 'Requesty API Key'
        this.name = 'requestyApi'
        this.version = 1.0
        this.description = 'Credential for Requesty API'
        this.inputs = [
            {
                label: 'Requesty API Key',
                name: 'requestyApiKey',
                type: 'password',
                description: 'API Key'
            }
        ]
    }

}

module.exports = { credClass: RequestyAPIAuth }
194 changes: 194 additions & 0 deletions packages/components/nodes/chatmodels/ChatRequesty/ChatRequesty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { ChatOpenAI as LangchainChatOpenAI, ChatOpenAIFields } from '@langchain/openai'
import { BaseCache } from '@langchain/core/caches'
import { ICommonObject, IMultiModalOption, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
import { ChatRequesty } from './FlowiseChatRequesty'

class ChatRequesty_ChatModels implements INode {
label: string
name: string
version: number
type: string
icon: string
category: string
description: string
baseClasses: string[]
credential: INodeParams
inputs: INodeParams[]

constructor() {
this.label = 'Requesty'
this.name = 'chatRequesty'
this.version = 1.0
this.type = 'ChatRequesty'
this.icon = 'requesty.svg'
this.category = 'Chat Models'
this.description = 'Wrapper around Requesty Inference API'
this.baseClasses = [this.type, ...getBaseClasses(LangchainChatOpenAI)]
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['requestyApi'],
optional: true
}
Comment on lines +28 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The credential for Requesty is currently marked as optional: true. Since Requesty is a paid cloud LLM gateway that requires authentication, making the credential required prevents users from saving the node without an API key, which would fail at runtime. It is highly recommended to make the credential required by removing optional: true.

        this.credential = {
            label: 'Connect Credential',
            name: 'credential',
            type: 'credential',
            credentialNames: ['requestyApi']
        }
References
  1. When a feature requires a specific configuration (e.g., an API key for a sandboxed environment), it is preferable to throw an error if the configuration is missing rather than silently falling back to a different implementation.

Comment on lines +28 to +34

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

Since ChatRequesty reuses the LangChain ChatOpenAI class under the hood, if the credential is left optional and not provided, LangChain will automatically fall back to the OPENAI_API_KEY environment variable if it is set. This would result in sending the user's OpenAI API key to the Requesty gateway (https://router.requesty.ai/v1), which is a security risk (unintended credential exposure to a third party).

To prevent this, the credential should be required by removing optional: true.

        this.credential = {
            label: 'Connect Credential',
            name: 'credential',
            type: 'credential',
            credentialNames: ['requestyApi']
        }
References
  1. When a feature requires a specific configuration (e.g., an API key for a sandboxed environment), it is preferable to throw an error if the configuration is missing rather than silently falling back to a different implementation.

this.inputs = [
{
label: 'Cache',
name: 'cache',
type: 'BaseCache',
optional: true
},
{
label: 'Model Name',
name: 'modelName',
type: 'string',
placeholder: 'openai/gpt-4o-mini'
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
default: 0.9,
optional: true
},
{
label: 'Streaming',
name: 'streaming',
type: 'boolean',
default: true,
optional: true,
additionalParams: true
},
{
label: 'Allow Image Uploads',
name: 'allowImageUploads',
type: 'boolean',
description:
'Allow image input. Refer to the <a href="https://docs.flowiseai.com/using-flowise/uploads#image" target="_blank">docs</a> for more details.',
default: false,
optional: true
},
{
label: 'Max Tokens',
name: 'maxTokens',
type: 'number',
step: 1,
optional: true,
additionalParams: true
},
{
label: 'Top Probability',
name: 'topP',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
},
{
label: 'Frequency Penalty',
name: 'frequencyPenalty',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
},
{
label: 'Presence Penalty',
name: 'presencePenalty',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
},
{
label: 'Timeout',
name: 'timeout',
type: 'number',
step: 1,
optional: true,
additionalParams: true
},
{
label: 'Base Path',
name: 'basepath',
type: 'string',
optional: true,
default: 'https://router.requesty.ai/v1',
description: 'Override the default base URL for the API, e.g., "https://api.example.com/v2/',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a missing closing double quote in the description string of the basepath input parameter (e.g., "https://api.example.com/v2/). Adding the closing double quote improves readability and correctness of the UI description.

Suggested change
description: 'Override the default base URL for the API, e.g., "https://api.example.com/v2/',
description: 'Override the default base URL for the API, e.g., "https://api.example.com/v2/"',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is a missing closing double quote in the basepath input description string.

Suggested change
description: 'Override the default base URL for the API, e.g., "https://api.example.com/v2/',
description: 'Override the default base URL for the API, e.g., "https://api.example.com/v2/"',

additionalParams: true
},
{
label: 'Base Options',
name: 'baseOptions',
type: 'json',
optional: true,
description: 'Default headers to include with every request to the API.',
additionalParams: true
}
]
}

async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const temperature = nodeData.inputs?.temperature as string
const modelName = nodeData.inputs?.modelName as string
const maxTokens = nodeData.inputs?.maxTokens as string
const topP = nodeData.inputs?.topP as string
const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string
const presencePenalty = nodeData.inputs?.presencePenalty as string
const timeout = nodeData.inputs?.timeout as string
const streaming = nodeData.inputs?.streaming as boolean
const basePath = (nodeData.inputs?.basepath as string) || 'https://router.requesty.ai/v1'
const baseOptions = nodeData.inputs?.baseOptions
const cache = nodeData.inputs?.cache as BaseCache
const allowImageUploads = nodeData.inputs?.allowImageUploads as boolean

const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const requestyApiKey = getCredentialParam('requestyApiKey', credentialData, nodeData)

const obj: ChatOpenAIFields = {
temperature: parseFloat(temperature),
modelName,
openAIApiKey: requestyApiKey,
apiKey: requestyApiKey,
streaming: streaming ?? true
}
Comment on lines +150 to +156

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If temperature is not provided or is cleared by the user, parseFloat(temperature) will return NaN. Passing NaN to the LangChain model configuration can cause unexpected behavior or runtime errors.

To prevent this, only set the temperature property on the configuration object if a valid value is provided, similar to how other optional numeric parameters are handled.

Suggested change
const obj: ChatOpenAIFields = {
temperature: parseFloat(temperature),
modelName,
openAIApiKey: requestyApiKey,
apiKey: requestyApiKey,
streaming: streaming ?? true
}
const obj: ChatOpenAIFields = {
modelName,
openAIApiKey: requestyApiKey,
apiKey: requestyApiKey,
streaming: streaming ?? true
}
if (temperature) obj.temperature = parseFloat(temperature)


if (maxTokens) obj.maxTokens = parseInt(maxTokens, 10)
if (topP) obj.topP = parseFloat(topP)
if (frequencyPenalty) obj.frequencyPenalty = parseFloat(frequencyPenalty)
if (presencePenalty) obj.presencePenalty = parseFloat(presencePenalty)
if (timeout) obj.timeout = parseInt(timeout, 10)
if (cache) obj.cache = cache

let parsedBaseOptions: any | undefined = undefined

if (baseOptions) {
try {
parsedBaseOptions = typeof baseOptions === 'object' ? baseOptions : JSON.parse(baseOptions)
} catch (exception) {
throw new Error("Invalid JSON in the ChatRequesty's BaseOptions: " + exception)
}
}

if (basePath || parsedBaseOptions) {
obj.configuration = {
baseURL: basePath,
defaultHeaders: parsedBaseOptions
}
}

const multiModalOption: IMultiModalOption = {
image: {
allowImageUploads: allowImageUploads ?? false
}
}

const model = new ChatRequesty(nodeData.id, obj)
model.setMultiModalOption(multiModalOption)
return model
}
}

module.exports = { nodeClass: ChatRequesty_ChatModels }
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ChatOpenAI as LangchainChatOpenAI, ChatOpenAIFields } from '@langchain/openai'
import { IMultiModalOption, IVisionChatModal } from '../../../src'

export class ChatRequesty extends LangchainChatOpenAI implements IVisionChatModal {
configuredModel: string
configuredMaxToken?: number
multiModalOption: IMultiModalOption
id: string

constructor(id: string, fields?: ChatOpenAIFields) {
super(fields)
this.id = id
this.configuredModel = fields?.modelName ?? ''
this.configuredMaxToken = fields?.maxTokens
}

setMultiModalOption(multiModalOption: IMultiModalOption): void {
this.multiModalOption = multiModalOption
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.