@alpic80/rivet-core 1.19.1-aidon.3 → 1.24.0-aidon.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/cjs/bundle.cjs +4512 -1240
- package/dist/cjs/bundle.cjs.map +4 -4
- package/dist/esm/api/createProcessor.js +8 -17
- package/dist/esm/api/looseDataValue.js +16 -0
- package/dist/esm/exports.js +2 -0
- package/dist/esm/integrations/CodeRunner.js +36 -0
- package/dist/esm/integrations/DatasetProvider.js +1 -1
- package/dist/esm/integrations/GptTokenizerTokenizer.js +7 -4
- package/dist/esm/integrations/openai/OpenAIEmbeddingGenerator.js +1 -1
- package/dist/esm/model/DataValue.js +14 -2
- package/dist/esm/model/GraphProcessor.js +276 -107
- package/dist/esm/model/NodeBase.js +11 -1
- package/dist/esm/model/NodeImpl.js +8 -0
- package/dist/esm/model/Nodes.js +31 -4
- package/dist/esm/model/ProjectReferenceLoader.js +1 -0
- package/dist/esm/model/nodes/AssembleMessageNode.js +12 -2
- package/dist/esm/model/nodes/AssemblePromptNode.js +22 -0
- package/dist/esm/model/nodes/CallGraphNode.js +3 -4
- package/dist/esm/model/nodes/ChatLoopNode.js +150 -0
- package/dist/esm/model/nodes/ChatNode.js +7 -934
- package/dist/esm/model/nodes/ChatNodeBase.js +1277 -0
- package/dist/esm/model/nodes/ChunkNode.js +2 -2
- package/dist/esm/model/nodes/CodeNode.js +40 -5
- package/dist/esm/model/nodes/CronNode.js +248 -0
- package/dist/esm/model/nodes/DelegateFunctionCallNode.js +37 -12
- package/dist/esm/model/nodes/DestructureNode.js +1 -1
- package/dist/esm/model/nodes/DocumentNode.js +183 -0
- package/dist/esm/model/nodes/ExtractJsonNode.js +4 -4
- package/dist/esm/model/nodes/ExtractRegexNode.js +10 -11
- package/dist/esm/model/nodes/GetAllDatasetsNode.js +1 -1
- package/dist/esm/model/nodes/GetEmbeddingNode.js +1 -1
- package/dist/esm/model/nodes/HttpCallNode.js +3 -1
- package/dist/esm/model/nodes/IfNode.js +5 -0
- package/dist/esm/model/nodes/LoopControllerNode.js +1 -1
- package/dist/esm/model/nodes/LoopUntilNode.js +214 -0
- package/dist/esm/model/nodes/ObjectNode.js +1 -1
- package/dist/esm/model/nodes/PromptNode.js +29 -6
- package/dist/esm/model/nodes/RaceInputsNode.js +1 -2
- package/dist/esm/model/nodes/ReadAllFilesNode.js +210 -0
- package/dist/esm/model/nodes/ReadDirectoryNode.js +31 -25
- package/dist/esm/model/nodes/ReferencedGraphAliasNode.js +199 -0
- package/dist/esm/model/nodes/ReplaceDatasetNode.js +1 -1
- package/dist/esm/model/nodes/SliceNode.js +0 -1
- package/dist/esm/model/nodes/SplitNode.js +1 -1
- package/dist/esm/model/nodes/SubGraphNode.js +0 -1
- package/dist/esm/model/nodes/TextNode.js +9 -4
- package/dist/esm/model/nodes/ToMarkdownTableNode.js +119 -0
- package/dist/esm/model/nodes/ToTreeNode.js +133 -0
- package/dist/esm/model/nodes/{GptFunctionNode.js → ToolNode.js} +10 -10
- package/dist/esm/model/nodes/UserInputNode.js +10 -12
- package/dist/esm/model/nodes/WriteFileNode.js +147 -0
- package/dist/esm/native/BrowserNativeApi.js +16 -1
- package/dist/esm/plugins/aidon/nodes/ChatAidonNode.js +5 -5
- package/dist/esm/plugins/anthropic/anthropic.js +29 -14
- package/dist/esm/plugins/anthropic/fetchEventSource.js +3 -2
- package/dist/esm/plugins/anthropic/nodes/ChatAnthropicNode.js +264 -147
- package/dist/esm/plugins/anthropic/plugin.js +9 -1
- package/dist/esm/plugins/assemblyAi/LemurQaNode.js +1 -1
- package/dist/esm/plugins/assemblyAi/LemurSummaryNode.js +1 -1
- package/dist/esm/plugins/gentrace/plugin.js +6 -6
- package/dist/esm/plugins/google/google.js +120 -6
- package/dist/esm/plugins/google/nodes/ChatGoogleNode.js +219 -56
- package/dist/esm/plugins/google/plugin.js +13 -6
- package/dist/esm/plugins/openai/nodes/RunThreadNode.js +2 -2
- package/dist/esm/plugins/openai/nodes/ThreadMessageNode.js +1 -1
- package/dist/esm/recording/ExecutionRecorder.js +59 -4
- package/dist/esm/utils/base64.js +13 -0
- package/dist/esm/utils/chatMessageToOpenAIChatCompletionMessage.js +15 -2
- package/dist/esm/utils/coerceType.js +4 -1
- package/dist/esm/utils/fetchEventSource.js +1 -1
- package/dist/esm/utils/interpolation.js +108 -3
- package/dist/esm/utils/openai.js +106 -50
- package/dist/esm/utils/paths.js +80 -0
- package/dist/esm/utils/serialization/serialization_v4.js +5 -0
- package/dist/types/api/createProcessor.d.ts +11 -5
- package/dist/types/api/looseDataValue.d.ts +4 -0
- package/dist/types/api/streaming.d.ts +1 -1
- package/dist/types/exports.d.ts +2 -0
- package/dist/types/integrations/CodeRunner.d.ts +18 -0
- package/dist/types/integrations/DatasetProvider.d.ts +1 -1
- package/dist/types/model/DataValue.d.ts +29 -6
- package/dist/types/model/EditorDefinition.d.ts +6 -1
- package/dist/types/model/GraphProcessor.d.ts +14 -7
- package/dist/types/model/NodeBase.d.ts +4 -0
- package/dist/types/model/NodeImpl.d.ts +5 -4
- package/dist/types/model/Nodes.d.ts +13 -4
- package/dist/types/model/ProcessContext.d.ts +16 -1
- package/dist/types/model/Project.d.ts +19 -7
- package/dist/types/model/ProjectReferenceLoader.d.ts +5 -0
- package/dist/types/model/RivetPlugin.d.ts +6 -0
- package/dist/types/model/RivetUIContext.d.ts +5 -1
- package/dist/types/model/Settings.d.ts +1 -0
- package/dist/types/model/nodes/AssemblePromptNode.d.ts +4 -1
- package/dist/types/model/nodes/ChatLoopNode.d.ts +21 -0
- package/dist/types/model/nodes/ChatNode.d.ts +2 -62
- package/dist/types/model/nodes/ChatNodeBase.d.ts +85 -0
- package/dist/types/model/nodes/CodeNode.d.ts +8 -2
- package/dist/types/model/nodes/CronNode.d.ts +34 -0
- package/dist/types/model/nodes/DelegateFunctionCallNode.d.ts +1 -0
- package/dist/types/model/nodes/DocumentNode.d.ts +28 -0
- package/dist/types/model/nodes/GetAllDatasetsNode.d.ts +2 -2
- package/dist/types/model/nodes/LoopUntilNode.d.ts +32 -0
- package/dist/types/model/nodes/ObjectNode.d.ts +2 -2
- package/dist/types/model/nodes/PromptNode.d.ts +2 -0
- package/dist/types/model/nodes/RaceInputsNode.d.ts +1 -2
- package/dist/types/model/nodes/ReadAllFilesNode.d.ts +30 -0
- package/dist/types/model/nodes/ReadDirectoryNode.d.ts +1 -1
- package/dist/types/model/nodes/ReferencedGraphAliasNode.d.ts +31 -0
- package/dist/types/model/nodes/SplitNode.d.ts +2 -2
- package/dist/types/model/nodes/ToMarkdownTableNode.d.ts +19 -0
- package/dist/types/model/nodes/ToTreeNode.d.ts +21 -0
- package/dist/types/model/nodes/UserInputNode.d.ts +2 -3
- package/dist/types/model/nodes/WriteFileNode.d.ts +23 -0
- package/dist/types/native/BrowserNativeApi.d.ts +8 -5
- package/dist/types/native/NativeApi.d.ts +12 -1
- package/dist/types/plugins/anthropic/anthropic.d.ts +94 -13
- package/dist/types/plugins/anthropic/nodes/ChatAnthropicNode.d.ts +7 -2
- package/dist/types/plugins/google/google.d.ts +101 -18
- package/dist/types/plugins/google/nodes/ChatGoogleNode.d.ts +3 -2
- package/dist/types/recording/RecordedEvents.d.ts +3 -0
- package/dist/types/utils/base64.d.ts +2 -1
- package/dist/types/utils/chatMessageToOpenAIChatCompletionMessage.d.ts +3 -1
- package/dist/types/utils/interpolation.d.ts +3 -0
- package/dist/types/utils/openai.d.ts +127 -21
- package/dist/types/utils/paths.d.ts +8 -0
- package/dist/types/utils/serialization/serialization_v3.d.ts +1 -0
- package/package.json +15 -11
- /package/dist/types/model/nodes/{GptFunctionNode.d.ts → ToolNode.d.ts} +0 -0
|
@@ -0,0 +1,1277 @@
|
|
|
1
|
+
import { match } from 'ts-pattern';
|
|
2
|
+
import { coerceType, coerceTypeOptional } from '../../utils/coerceType.js';
|
|
3
|
+
import { getError } from '../../utils/errors.js';
|
|
4
|
+
import { dedent } from '../../utils/misc.js';
|
|
5
|
+
import { OpenAIError, openAiModelOptions, openaiModels, chatCompletions, streamChatCompletions, defaultOpenaiSupported, } from '../../utils/openai.js';
|
|
6
|
+
import { isArrayDataValue, getScalarTypeOf } from '../DataValue.js';
|
|
7
|
+
import { cleanHeaders, getInputOrData } from '../../utils/inputs.js';
|
|
8
|
+
import { chatMessageToOpenAIChatCompletionMessage } from '../../utils/chatMessageToOpenAIChatCompletionMessage.js';
|
|
9
|
+
import { DEFAULT_CHAT_ENDPOINT } from '../../utils/defaults.js';
|
|
10
|
+
import { addWarning } from '../../utils/outputs.js';
|
|
11
|
+
import retry from 'p-retry';
|
|
12
|
+
import { base64ToUint8Array } from '../../utils/base64.js';
|
|
13
|
+
// Temporary
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
export const ChatNodeBase = {
|
|
16
|
+
defaultData: () => ({
|
|
17
|
+
model: 'gpt-4o-mini',
|
|
18
|
+
useModelInput: false,
|
|
19
|
+
temperature: 0.5,
|
|
20
|
+
useTemperatureInput: false,
|
|
21
|
+
top_p: 1,
|
|
22
|
+
useTopPInput: false,
|
|
23
|
+
useTopP: false,
|
|
24
|
+
useUseTopPInput: false,
|
|
25
|
+
maxTokens: 1024,
|
|
26
|
+
useMaxTokensInput: false,
|
|
27
|
+
useStop: false,
|
|
28
|
+
stop: '',
|
|
29
|
+
useStopInput: false,
|
|
30
|
+
presencePenalty: undefined,
|
|
31
|
+
usePresencePenaltyInput: false,
|
|
32
|
+
frequencyPenalty: undefined,
|
|
33
|
+
useFrequencyPenaltyInput: false,
|
|
34
|
+
user: undefined,
|
|
35
|
+
useUserInput: false,
|
|
36
|
+
enableFunctionUse: false,
|
|
37
|
+
cache: false,
|
|
38
|
+
useAsGraphPartialOutput: true,
|
|
39
|
+
parallelFunctionCalling: true,
|
|
40
|
+
additionalParameters: [],
|
|
41
|
+
useAdditionalParametersInput: false,
|
|
42
|
+
useServerTokenCalculation: true,
|
|
43
|
+
outputUsage: false,
|
|
44
|
+
usePredictedOutput: false,
|
|
45
|
+
modalitiesIncludeAudio: false,
|
|
46
|
+
modalitiesIncludeText: false,
|
|
47
|
+
reasoningEffort: '',
|
|
48
|
+
useReasoningEffortInput: false,
|
|
49
|
+
}),
|
|
50
|
+
getInputDefinitions: (data) => {
|
|
51
|
+
const inputs = [];
|
|
52
|
+
if (data.useEndpointInput) {
|
|
53
|
+
inputs.push({
|
|
54
|
+
dataType: 'string',
|
|
55
|
+
id: 'endpoint',
|
|
56
|
+
title: 'Endpoint',
|
|
57
|
+
description: 'The endpoint to use for the OpenAI API. You can use this to replace with any OpenAI-compatible API. Leave blank for the default: https://api.openai.com/api/v1/chat/completions',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
inputs.push({
|
|
61
|
+
id: 'systemPrompt',
|
|
62
|
+
title: 'System Prompt',
|
|
63
|
+
dataType: 'string',
|
|
64
|
+
required: false,
|
|
65
|
+
description: 'The system prompt to send to the model.',
|
|
66
|
+
coerced: true,
|
|
67
|
+
});
|
|
68
|
+
if (data.useModelInput) {
|
|
69
|
+
inputs.push({
|
|
70
|
+
id: 'model',
|
|
71
|
+
title: 'Model',
|
|
72
|
+
dataType: 'string',
|
|
73
|
+
required: false,
|
|
74
|
+
description: 'The model to use for the chat.',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (data.useTemperatureInput) {
|
|
78
|
+
inputs.push({
|
|
79
|
+
dataType: 'number',
|
|
80
|
+
id: 'temperature',
|
|
81
|
+
title: 'Temperature',
|
|
82
|
+
description: 'What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (data.useTopPInput) {
|
|
86
|
+
inputs.push({
|
|
87
|
+
dataType: 'number',
|
|
88
|
+
id: 'top_p',
|
|
89
|
+
title: 'Top P',
|
|
90
|
+
description: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
if (data.useUseTopPInput) {
|
|
94
|
+
inputs.push({
|
|
95
|
+
dataType: 'boolean',
|
|
96
|
+
id: 'useTopP',
|
|
97
|
+
title: 'Use Top P',
|
|
98
|
+
description: 'Whether to use top p sampling, or temperature sampling.',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (data.useMaxTokensInput) {
|
|
102
|
+
inputs.push({
|
|
103
|
+
dataType: 'number',
|
|
104
|
+
id: 'maxTokens',
|
|
105
|
+
title: 'Max Tokens',
|
|
106
|
+
description: 'The maximum number of tokens to generate in the chat completion.',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (data.useStopInput) {
|
|
110
|
+
inputs.push({
|
|
111
|
+
dataType: 'string',
|
|
112
|
+
id: 'stop',
|
|
113
|
+
title: 'Stop',
|
|
114
|
+
description: 'A sequence where the API will stop generating further tokens.',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (data.usePresencePenaltyInput) {
|
|
118
|
+
inputs.push({
|
|
119
|
+
dataType: 'number',
|
|
120
|
+
id: 'presencePenalty',
|
|
121
|
+
title: 'Presence Penalty',
|
|
122
|
+
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (data.useFrequencyPenaltyInput) {
|
|
126
|
+
inputs.push({
|
|
127
|
+
dataType: 'number',
|
|
128
|
+
id: 'frequencyPenalty',
|
|
129
|
+
title: 'Frequency Penalty',
|
|
130
|
+
description: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
if (data.useUserInput) {
|
|
134
|
+
inputs.push({
|
|
135
|
+
dataType: 'string',
|
|
136
|
+
id: 'user',
|
|
137
|
+
title: 'User',
|
|
138
|
+
description: 'A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (data.useNumberOfChoicesInput) {
|
|
142
|
+
inputs.push({
|
|
143
|
+
dataType: 'number',
|
|
144
|
+
id: 'numberOfChoices',
|
|
145
|
+
title: 'Number of Choices',
|
|
146
|
+
description: 'If greater than 1, the model will return multiple choices and the response will be an array.',
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
if (data.useHeadersInput) {
|
|
150
|
+
inputs.push({
|
|
151
|
+
dataType: 'object',
|
|
152
|
+
id: 'headers',
|
|
153
|
+
title: 'Headers',
|
|
154
|
+
description: 'Additional headers to send to the API.',
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
inputs.push({
|
|
158
|
+
dataType: ['chat-message', 'chat-message[]'],
|
|
159
|
+
id: 'prompt',
|
|
160
|
+
title: 'Prompt',
|
|
161
|
+
description: 'The prompt message or messages to send to the model.',
|
|
162
|
+
coerced: true,
|
|
163
|
+
});
|
|
164
|
+
if (data.enableFunctionUse) {
|
|
165
|
+
inputs.push({
|
|
166
|
+
dataType: ['gpt-function', 'gpt-function[]'],
|
|
167
|
+
id: 'functions',
|
|
168
|
+
title: 'Functions',
|
|
169
|
+
description: 'Functions to use in the model. To connect multiple functions, use an Array node.',
|
|
170
|
+
coerced: false,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (data.useSeedInput) {
|
|
174
|
+
inputs.push({
|
|
175
|
+
dataType: 'number',
|
|
176
|
+
id: 'seed',
|
|
177
|
+
title: 'Seed',
|
|
178
|
+
coerced: true,
|
|
179
|
+
description: 'If specified, OpenAI will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (data.useToolChoiceInput) {
|
|
183
|
+
inputs.push({
|
|
184
|
+
dataType: 'string',
|
|
185
|
+
id: 'toolChoice',
|
|
186
|
+
title: 'Tool Choice',
|
|
187
|
+
coerced: true,
|
|
188
|
+
description: 'Controls which (if any) function is called by the model. `none` is the default when no functions are present. `auto` is the default if functions are present. `function` forces the model to call a function.',
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
if (data.useToolChoiceInput || data.useToolChoiceFunctionInput) {
|
|
192
|
+
inputs.push({
|
|
193
|
+
dataType: 'string',
|
|
194
|
+
id: 'toolChoiceFunction',
|
|
195
|
+
title: 'Tool Choice Function',
|
|
196
|
+
coerced: true,
|
|
197
|
+
description: 'The name of the function to force the model to call.',
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (data.useResponseFormatInput) {
|
|
201
|
+
inputs.push({
|
|
202
|
+
dataType: 'string',
|
|
203
|
+
id: 'responseFormat',
|
|
204
|
+
title: 'Response Format',
|
|
205
|
+
coerced: true,
|
|
206
|
+
description: 'The format to force the model to reply in.',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
if (data.useAdditionalParametersInput) {
|
|
210
|
+
inputs.push({
|
|
211
|
+
dataType: 'object',
|
|
212
|
+
id: 'additionalParameters',
|
|
213
|
+
title: 'Additional Parameters',
|
|
214
|
+
description: 'Additional chat completion parameters to send to the API.',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (data.responseFormat === 'json_schema') {
|
|
218
|
+
inputs.push({
|
|
219
|
+
dataType: 'object',
|
|
220
|
+
id: 'responseSchema',
|
|
221
|
+
title: 'Response Schema',
|
|
222
|
+
description: 'The JSON schema that the response will adhere to (Structured Outputs).',
|
|
223
|
+
required: true,
|
|
224
|
+
});
|
|
225
|
+
if (data.useResponseSchemaNameInput) {
|
|
226
|
+
inputs.push({
|
|
227
|
+
dataType: 'string',
|
|
228
|
+
id: 'responseSchemaName',
|
|
229
|
+
title: 'Response Schema Name',
|
|
230
|
+
description: 'The name of the JSON schema that the response will adhere to (Structured Outputs).',
|
|
231
|
+
required: false,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (data.usePredictedOutput) {
|
|
236
|
+
inputs.push({
|
|
237
|
+
dataType: 'string[]',
|
|
238
|
+
id: 'predictedOutput',
|
|
239
|
+
title: 'Predicted Output',
|
|
240
|
+
description: 'The predicted output from the model.',
|
|
241
|
+
coerced: true,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
if (data.useAudioVoiceInput) {
|
|
245
|
+
inputs.push({
|
|
246
|
+
dataType: 'string',
|
|
247
|
+
id: 'audioVoice',
|
|
248
|
+
title: 'Audio Voice',
|
|
249
|
+
description: 'The voice to use for audio responses. See your model for supported voices.',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (data.useAudioFormatInput) {
|
|
253
|
+
inputs.push({
|
|
254
|
+
dataType: 'string',
|
|
255
|
+
id: 'audioFormat',
|
|
256
|
+
title: 'Audio Format',
|
|
257
|
+
description: 'The format to use for audio responses.',
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return inputs;
|
|
261
|
+
},
|
|
262
|
+
getOutputDefinitions: (data) => {
|
|
263
|
+
const outputs = [];
|
|
264
|
+
if (data.useNumberOfChoicesInput || (data.numberOfChoices ?? 1) > 1) {
|
|
265
|
+
outputs.push({
|
|
266
|
+
dataType: 'string[]',
|
|
267
|
+
id: 'response',
|
|
268
|
+
title: 'Responses',
|
|
269
|
+
description: 'All responses from the model.',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
outputs.push({
|
|
274
|
+
dataType: 'string',
|
|
275
|
+
id: 'response',
|
|
276
|
+
title: 'Response',
|
|
277
|
+
description: 'The textual response from the model.',
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
if (data.enableFunctionUse) {
|
|
281
|
+
if (data.parallelFunctionCalling) {
|
|
282
|
+
outputs.push({
|
|
283
|
+
dataType: 'object[]',
|
|
284
|
+
id: 'function-calls',
|
|
285
|
+
title: 'Function Calls',
|
|
286
|
+
description: 'The function calls that were made, if any.',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
outputs.push({
|
|
291
|
+
dataType: 'object',
|
|
292
|
+
id: 'function-call',
|
|
293
|
+
title: 'Function Call',
|
|
294
|
+
description: 'The function call that was made, if any.',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
outputs.push({
|
|
299
|
+
dataType: 'chat-message[]',
|
|
300
|
+
id: 'in-messages',
|
|
301
|
+
title: 'Messages Sent',
|
|
302
|
+
description: 'All messages sent to the model.',
|
|
303
|
+
});
|
|
304
|
+
if (!(data.useNumberOfChoicesInput || (data.numberOfChoices ?? 1) > 1)) {
|
|
305
|
+
outputs.push({
|
|
306
|
+
dataType: 'chat-message[]',
|
|
307
|
+
id: 'all-messages',
|
|
308
|
+
title: 'All Messages',
|
|
309
|
+
description: 'All messages, with the response appended.',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
outputs.push({
|
|
313
|
+
dataType: 'number',
|
|
314
|
+
id: 'responseTokens',
|
|
315
|
+
title: 'Response Tokens',
|
|
316
|
+
description: 'The number of tokens in the response from the LLM. For a multi-response, this is the sum.',
|
|
317
|
+
});
|
|
318
|
+
if (data.outputUsage) {
|
|
319
|
+
outputs.push({
|
|
320
|
+
dataType: 'object',
|
|
321
|
+
id: 'usage',
|
|
322
|
+
title: 'Usage',
|
|
323
|
+
description: 'Usage statistics for the model.',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
if (data.modalitiesIncludeAudio) {
|
|
327
|
+
outputs.push({
|
|
328
|
+
dataType: 'audio',
|
|
329
|
+
id: 'audio',
|
|
330
|
+
title: 'Audio',
|
|
331
|
+
description: 'The audio response from the model.',
|
|
332
|
+
});
|
|
333
|
+
outputs.push({
|
|
334
|
+
dataType: 'string',
|
|
335
|
+
id: 'audioTranscript',
|
|
336
|
+
title: 'Transcript',
|
|
337
|
+
description: 'The transcript of the audio response.',
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return outputs;
|
|
341
|
+
},
|
|
342
|
+
getEditors: () => {
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
type: 'dropdown',
|
|
346
|
+
label: 'GPT Model',
|
|
347
|
+
dataKey: 'model',
|
|
348
|
+
useInputToggleDataKey: 'useModelInput',
|
|
349
|
+
options: openAiModelOptions,
|
|
350
|
+
disableIf: (data) => {
|
|
351
|
+
return !!data.overrideModel?.trim();
|
|
352
|
+
},
|
|
353
|
+
helperMessage: (data) => {
|
|
354
|
+
if (data.overrideModel?.trim()) {
|
|
355
|
+
return `Model overridden to: ${data.overrideModel}`;
|
|
356
|
+
}
|
|
357
|
+
if (data.model === 'local-model') {
|
|
358
|
+
return 'Local model is an indicator for your own convenience, it does not affect the local LLM used.';
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
type: 'group',
|
|
364
|
+
label: 'Parameters',
|
|
365
|
+
editors: [
|
|
366
|
+
{
|
|
367
|
+
type: 'number',
|
|
368
|
+
label: 'Temperature',
|
|
369
|
+
dataKey: 'temperature',
|
|
370
|
+
useInputToggleDataKey: 'useTemperatureInput',
|
|
371
|
+
min: 0,
|
|
372
|
+
max: 2,
|
|
373
|
+
step: 0.1,
|
|
374
|
+
helperMessage: 'What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.',
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
type: 'number',
|
|
378
|
+
label: 'Top P',
|
|
379
|
+
dataKey: 'top_p',
|
|
380
|
+
useInputToggleDataKey: 'useTopPInput',
|
|
381
|
+
min: 0,
|
|
382
|
+
max: 1,
|
|
383
|
+
step: 0.1,
|
|
384
|
+
helperMessage: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.',
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
type: 'toggle',
|
|
388
|
+
label: 'Use Top P',
|
|
389
|
+
dataKey: 'useTopP',
|
|
390
|
+
useInputToggleDataKey: 'useUseTopPInput',
|
|
391
|
+
helperMessage: 'Whether to use top p sampling, or temperature sampling.',
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
type: 'number',
|
|
395
|
+
label: 'Max Tokens',
|
|
396
|
+
dataKey: 'maxTokens',
|
|
397
|
+
useInputToggleDataKey: 'useMaxTokensInput',
|
|
398
|
+
min: 0,
|
|
399
|
+
max: Number.MAX_SAFE_INTEGER,
|
|
400
|
+
step: 1,
|
|
401
|
+
helperMessage: 'The maximum number of tokens to generate in the chat completion.',
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
type: 'string',
|
|
405
|
+
label: 'Stop',
|
|
406
|
+
dataKey: 'stop',
|
|
407
|
+
useInputToggleDataKey: 'useStopInput',
|
|
408
|
+
helperMessage: 'A sequence where the API will stop generating further tokens.',
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
type: 'number',
|
|
412
|
+
label: 'Presence Penalty',
|
|
413
|
+
dataKey: 'presencePenalty',
|
|
414
|
+
useInputToggleDataKey: 'usePresencePenaltyInput',
|
|
415
|
+
min: 0,
|
|
416
|
+
max: 2,
|
|
417
|
+
step: 0.1,
|
|
418
|
+
allowEmpty: true,
|
|
419
|
+
helperMessage: `Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.`,
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
type: 'number',
|
|
423
|
+
label: 'Frequency Penalty',
|
|
424
|
+
dataKey: 'frequencyPenalty',
|
|
425
|
+
useInputToggleDataKey: 'useFrequencyPenaltyInput',
|
|
426
|
+
min: 0,
|
|
427
|
+
max: 2,
|
|
428
|
+
step: 0.1,
|
|
429
|
+
allowEmpty: true,
|
|
430
|
+
helperMessage: `Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.`,
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
type: 'dropdown',
|
|
434
|
+
label: 'Reasoning Effort',
|
|
435
|
+
dataKey: 'reasoningEffort',
|
|
436
|
+
useInputToggleDataKey: 'useReasoningEffortInput',
|
|
437
|
+
options: [
|
|
438
|
+
{ value: '', label: 'Unset' },
|
|
439
|
+
{ value: 'low', label: 'Low' },
|
|
440
|
+
{ value: 'medium', label: 'Medium' },
|
|
441
|
+
{ value: 'high', label: 'High' },
|
|
442
|
+
],
|
|
443
|
+
defaultValue: '',
|
|
444
|
+
helperMessage: 'Adjust the level of reasoning depth the model should apply. Only applies to reasoning models such as o3-mini.',
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
type: 'dropdown',
|
|
448
|
+
label: 'Response Format',
|
|
449
|
+
dataKey: 'responseFormat',
|
|
450
|
+
useInputToggleDataKey: 'useResponseFormatInput',
|
|
451
|
+
options: [
|
|
452
|
+
{ value: '', label: 'Default' },
|
|
453
|
+
{ value: 'text', label: 'Text' },
|
|
454
|
+
{ value: 'json', label: 'JSON Object' },
|
|
455
|
+
{ value: 'json_schema', label: 'JSON Schema' },
|
|
456
|
+
],
|
|
457
|
+
defaultValue: '',
|
|
458
|
+
helperMessage: 'The format to force the model to reply in.',
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
type: 'string',
|
|
462
|
+
label: 'Response Schema Name',
|
|
463
|
+
dataKey: 'responseSchemaName',
|
|
464
|
+
useInputToggleDataKey: 'useResponseSchemaNameInput',
|
|
465
|
+
helperMessage: 'The name of the JSON schema that the response will adhere to (Structured Outputs). Defaults to response_schema',
|
|
466
|
+
hideIf: (data) => data.responseFormat !== 'json_schema',
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
type: 'number',
|
|
470
|
+
label: 'Seed',
|
|
471
|
+
dataKey: 'seed',
|
|
472
|
+
useInputToggleDataKey: 'useSeedInput',
|
|
473
|
+
step: 1,
|
|
474
|
+
allowEmpty: true,
|
|
475
|
+
helperMessage: 'If specified, OpenAI will make a best effort to sample deterministically, such that repeated requests with the same `seed` and parameters should return the same result.',
|
|
476
|
+
},
|
|
477
|
+
],
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
type: 'group',
|
|
481
|
+
label: 'GPT Tools',
|
|
482
|
+
editors: [
|
|
483
|
+
{
|
|
484
|
+
type: 'toggle',
|
|
485
|
+
label: 'Enable Function Use',
|
|
486
|
+
dataKey: 'enableFunctionUse',
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
type: 'toggle',
|
|
490
|
+
label: 'Enable Parallel Function Calling',
|
|
491
|
+
dataKey: 'parallelFunctionCalling',
|
|
492
|
+
hideIf: (data) => !data.enableFunctionUse,
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
type: 'dropdown',
|
|
496
|
+
label: 'Tool Choice',
|
|
497
|
+
dataKey: 'toolChoice',
|
|
498
|
+
useInputToggleDataKey: 'useToolChoiceInput',
|
|
499
|
+
options: [
|
|
500
|
+
{ value: '', label: 'Default' },
|
|
501
|
+
{ value: 'none', label: 'None' },
|
|
502
|
+
{ value: 'auto', label: 'Auto' },
|
|
503
|
+
{ value: 'function', label: 'Function' },
|
|
504
|
+
{ value: 'required', label: 'Required' },
|
|
505
|
+
],
|
|
506
|
+
defaultValue: '',
|
|
507
|
+
helperMessage: 'Controls which (if any) function is called by the model. None is the default when no functions are present. Auto is the default if functions are present.',
|
|
508
|
+
hideIf: (data) => !data.enableFunctionUse,
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
type: 'string',
|
|
512
|
+
label: 'Tool Choice Function',
|
|
513
|
+
dataKey: 'toolChoiceFunction',
|
|
514
|
+
useInputToggleDataKey: 'useToolChoiceFunctionInput',
|
|
515
|
+
helperMessage: 'The name of the function to force the model to call.',
|
|
516
|
+
hideIf: (data) => data.toolChoice !== 'function' || !data.enableFunctionUse,
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
type: 'group',
|
|
522
|
+
label: 'Features',
|
|
523
|
+
editors: [
|
|
524
|
+
{
|
|
525
|
+
type: 'toggle',
|
|
526
|
+
label: 'Enable Predicted Output',
|
|
527
|
+
dataKey: 'usePredictedOutput',
|
|
528
|
+
helperMessage: 'If on, enables an input port for the predicted output from the model, when many of the output tokens are known ahead of time.',
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
type: 'toggle',
|
|
532
|
+
label: 'Modalities: Text',
|
|
533
|
+
dataKey: 'modalitiesIncludeText',
|
|
534
|
+
helperMessage: 'If on, the model will include text in its responses. Only relevant for multimodal models.',
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
type: 'toggle',
|
|
538
|
+
label: 'Modalities: Audio',
|
|
539
|
+
dataKey: 'modalitiesIncludeAudio',
|
|
540
|
+
helperMessage: 'If on, the model will include audio in its responses. Only relevant for multimodal models.',
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
type: 'string',
|
|
544
|
+
label: 'Audio Voice',
|
|
545
|
+
dataKey: 'audioVoice',
|
|
546
|
+
useInputToggleDataKey: 'useAudioVoiceInput',
|
|
547
|
+
helperMessage: 'The voice to use for audio responses. See your model for supported voices. OpenAI voices are: alloy, ash, coral, echo, fable, onyx, nova, sage, and shimmer.',
|
|
548
|
+
hideIf: (data) => !data.modalitiesIncludeAudio,
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
type: 'dropdown',
|
|
552
|
+
label: 'Audio Format',
|
|
553
|
+
dataKey: 'audioFormat',
|
|
554
|
+
useInputToggleDataKey: 'useAudioFormatInput',
|
|
555
|
+
options: [
|
|
556
|
+
{ value: 'wav', label: 'WAV' },
|
|
557
|
+
{ value: 'mp3', label: 'MP3' },
|
|
558
|
+
{ value: 'flac', label: 'FLAC' },
|
|
559
|
+
{ value: 'opus', label: 'OPUS' },
|
|
560
|
+
{ value: 'pcm16', label: 'PCM16' },
|
|
561
|
+
],
|
|
562
|
+
defaultValue: 'wav',
|
|
563
|
+
hideIf: (data) => !data.modalitiesIncludeAudio,
|
|
564
|
+
},
|
|
565
|
+
],
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
type: 'group',
|
|
569
|
+
label: 'Advanced',
|
|
570
|
+
editors: [
|
|
571
|
+
{
|
|
572
|
+
type: 'toggle',
|
|
573
|
+
label: 'Use Server Token Calculation',
|
|
574
|
+
dataKey: 'useServerTokenCalculation',
|
|
575
|
+
helperMessage: 'If on, do not calculate token counts on the client side, and rely on the server providing the token count.',
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
type: 'toggle',
|
|
579
|
+
label: 'Output Usage Statistics',
|
|
580
|
+
dataKey: 'outputUsage',
|
|
581
|
+
helperMessage: 'If on, output usage statistics for the model, such as token counts and cost.',
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
type: 'string',
|
|
585
|
+
label: 'User',
|
|
586
|
+
dataKey: 'user',
|
|
587
|
+
useInputToggleDataKey: 'useUserInput',
|
|
588
|
+
helperMessage: 'A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.',
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
type: 'number',
|
|
592
|
+
label: 'Number of Choices',
|
|
593
|
+
dataKey: 'numberOfChoices',
|
|
594
|
+
useInputToggleDataKey: 'useNumberOfChoicesInput',
|
|
595
|
+
min: 1,
|
|
596
|
+
max: 10,
|
|
597
|
+
step: 1,
|
|
598
|
+
defaultValue: 1,
|
|
599
|
+
helperMessage: 'If greater than 1, the model will return multiple choices and the response will be an array.',
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
type: 'string',
|
|
603
|
+
label: 'Endpoint',
|
|
604
|
+
dataKey: 'endpoint',
|
|
605
|
+
useInputToggleDataKey: 'useEndpointInput',
|
|
606
|
+
helperMessage: 'The endpoint to use for the OpenAI API. You can use this to replace with any OpenAI-compatible API. Leave blank for the default: https://api.openai.com/api/v1/chat/completions',
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
type: 'string',
|
|
610
|
+
label: 'Custom Model',
|
|
611
|
+
dataKey: 'overrideModel',
|
|
612
|
+
helperMessage: 'Overrides the model selected above with a custom string for the model.',
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
type: 'number',
|
|
616
|
+
label: 'Custom Max Tokens',
|
|
617
|
+
dataKey: 'overrideMaxTokens',
|
|
618
|
+
allowEmpty: true,
|
|
619
|
+
helperMessage: 'Overrides the max number of tokens a model can support. Leave blank for preconfigured token limits.',
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
type: 'keyValuePair',
|
|
623
|
+
label: 'Headers',
|
|
624
|
+
dataKey: 'headers',
|
|
625
|
+
useInputToggleDataKey: 'useHeadersInput',
|
|
626
|
+
keyPlaceholder: 'Header',
|
|
627
|
+
helperMessage: 'Additional headers to send to the API.',
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
type: 'toggle',
|
|
631
|
+
label: 'Cache In Rivet',
|
|
632
|
+
dataKey: 'cache',
|
|
633
|
+
helperMessage: 'If on, requests with the same parameters and messages will be cached in Rivet, for immediate responses without an API call.',
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
type: 'toggle',
|
|
637
|
+
label: 'Use for subgraph partial output',
|
|
638
|
+
dataKey: 'useAsGraphPartialOutput',
|
|
639
|
+
helperMessage: 'If on, streaming responses from this node will be shown in Subgraph nodes that call this graph.',
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
type: 'keyValuePair',
|
|
643
|
+
label: 'Additional Parameters',
|
|
644
|
+
dataKey: 'additionalParameters',
|
|
645
|
+
useInputToggleDataKey: 'useAdditionalParametersInput',
|
|
646
|
+
keyPlaceholder: 'Parameter',
|
|
647
|
+
valuePlaceholder: 'Value',
|
|
648
|
+
helperMessage: 'Additional chat completion parameters to send to the API. If the value appears to be a number, it will be sent as a number.',
|
|
649
|
+
},
|
|
650
|
+
],
|
|
651
|
+
},
|
|
652
|
+
];
|
|
653
|
+
},
|
|
654
|
+
getBody: (data) => {
|
|
655
|
+
return dedent `
|
|
656
|
+
${data.endpoint ? `${data.endpoint}` : ''}
|
|
657
|
+
${data.useMaxTokensInput ? 'Max Tokens: (Using Input)' : `${data.maxTokens} tokens`}
|
|
658
|
+
Model: ${data.useModelInput ? '(Using Input)' : data.overrideModel || data.model}
|
|
659
|
+
${data.useTopP ? 'Top P' : 'Temperature'}:
|
|
660
|
+
${data.useTopP
|
|
661
|
+
? data.useTopPInput
|
|
662
|
+
? '(Using Input)'
|
|
663
|
+
: data.top_p
|
|
664
|
+
: data.useTemperatureInput
|
|
665
|
+
? '(Using Input)'
|
|
666
|
+
: data.temperature}
|
|
667
|
+
${data.useStop ? `Stop: ${data.useStopInput ? '(Using Input)' : data.stop}` : ''}
|
|
668
|
+
${(data.frequencyPenalty ?? 0) !== 0
|
|
669
|
+
? `Frequency Penalty: ${data.useFrequencyPenaltyInput ? '(Using Input)' : data.frequencyPenalty}`
|
|
670
|
+
: ''}
|
|
671
|
+
${(data.presencePenalty ?? 0) !== 0
|
|
672
|
+
? `Presence Penalty: ${data.usePresencePenaltyInput ? '(Using Input)' : data.presencePenalty}`
|
|
673
|
+
: ''}
|
|
674
|
+
`.trim();
|
|
675
|
+
},
|
|
676
|
+
process: async (data, node, inputs, context) => {
|
|
677
|
+
const output = {};
|
|
678
|
+
const model = getInputOrData(data, inputs, 'model');
|
|
679
|
+
const temperature = getInputOrData(data, inputs, 'temperature', 'number');
|
|
680
|
+
const topP = data.useTopPInput ? coerceTypeOptional(inputs['top_p'], 'number') ?? data.top_p : data.top_p;
|
|
681
|
+
const useTopP = getInputOrData(data, inputs, 'useTopP', 'boolean');
|
|
682
|
+
const stop = data.useStopInput
|
|
683
|
+
? data.useStop
|
|
684
|
+
? coerceTypeOptional(inputs['stop'], 'string') ?? data.stop
|
|
685
|
+
: undefined
|
|
686
|
+
: data.stop;
|
|
687
|
+
const presencePenalty = getInputOrData(data, inputs, 'presencePenalty', 'number');
|
|
688
|
+
const frequencyPenalty = getInputOrData(data, inputs, 'frequencyPenalty', 'number');
|
|
689
|
+
const numberOfChoices = getInputOrData(data, inputs, 'numberOfChoices', 'number');
|
|
690
|
+
const endpoint = getInputOrData(data, inputs, 'endpoint');
|
|
691
|
+
const overrideModel = getInputOrData(data, inputs, 'overrideModel');
|
|
692
|
+
const seed = getInputOrData(data, inputs, 'seed', 'number');
|
|
693
|
+
const responseFormat = getInputOrData(data, inputs, 'responseFormat');
|
|
694
|
+
const toolChoiceMode = getInputOrData(data, inputs, 'toolChoice', 'string');
|
|
695
|
+
const parallelFunctionCalling = getInputOrData(data, inputs, 'parallelFunctionCalling', 'boolean');
|
|
696
|
+
const predictedOutput = data.usePredictedOutput
|
|
697
|
+
? coerceTypeOptional(inputs['predictedOutput'], 'string[]')
|
|
698
|
+
: undefined;
|
|
699
|
+
const toolChoice = !toolChoiceMode || !data.enableFunctionUse
|
|
700
|
+
? undefined
|
|
701
|
+
: toolChoiceMode === 'function'
|
|
702
|
+
? {
|
|
703
|
+
type: 'function',
|
|
704
|
+
function: {
|
|
705
|
+
name: getInputOrData(data, inputs, 'toolChoiceFunction', 'string'),
|
|
706
|
+
},
|
|
707
|
+
}
|
|
708
|
+
: toolChoiceMode;
|
|
709
|
+
let responseSchema;
|
|
710
|
+
const responseSchemaInput = inputs['responseSchema'];
|
|
711
|
+
if (responseSchemaInput?.type === 'gpt-function') {
|
|
712
|
+
responseSchema = responseSchemaInput.value.parameters;
|
|
713
|
+
}
|
|
714
|
+
else if (responseSchemaInput != null) {
|
|
715
|
+
responseSchema = coerceType(responseSchemaInput, 'object');
|
|
716
|
+
}
|
|
717
|
+
const openaiResponseFormat = !responseFormat?.trim()
|
|
718
|
+
? undefined
|
|
719
|
+
: responseFormat === 'json'
|
|
720
|
+
? {
|
|
721
|
+
type: 'json_object',
|
|
722
|
+
}
|
|
723
|
+
: responseFormat === 'json_schema'
|
|
724
|
+
? {
|
|
725
|
+
type: 'json_schema',
|
|
726
|
+
json_schema: {
|
|
727
|
+
name: getInputOrData(data, inputs, 'responseSchemaName', 'string') || 'response_schema',
|
|
728
|
+
strict: true,
|
|
729
|
+
schema: responseSchema ?? {},
|
|
730
|
+
},
|
|
731
|
+
}
|
|
732
|
+
: {
|
|
733
|
+
type: 'text',
|
|
734
|
+
};
|
|
735
|
+
const headersFromData = (data.headers ?? []).reduce((acc, header) => {
|
|
736
|
+
acc[header.key] = header.value;
|
|
737
|
+
return acc;
|
|
738
|
+
}, {});
|
|
739
|
+
const additionalHeaders = data.useHeadersInput
|
|
740
|
+
? coerceTypeOptional(inputs['headers'], 'object') ??
|
|
741
|
+
headersFromData
|
|
742
|
+
: headersFromData;
|
|
743
|
+
const additionalParametersFromData = (data.additionalParameters ?? []).reduce((acc, param) => {
|
|
744
|
+
acc[param.key] = Number.isNaN(parseFloat(param.value)) ? param.value : parseFloat(param.value);
|
|
745
|
+
return acc;
|
|
746
|
+
}, {});
|
|
747
|
+
const additionalParameters = data.useAdditionalParametersInput
|
|
748
|
+
? coerceTypeOptional(inputs['additionalParameters'], 'object') ?? additionalParametersFromData
|
|
749
|
+
: additionalParametersFromData;
|
|
750
|
+
// If using a model input, that's priority, otherwise override > main
|
|
751
|
+
const finalModel = data.useModelInput && inputs['model'] != null ? model : overrideModel || model;
|
|
752
|
+
const functions = coerceTypeOptional(inputs['functions'], 'gpt-function[]');
|
|
753
|
+
const tools = (functions ?? []).map((fn) => ({
|
|
754
|
+
function: fn,
|
|
755
|
+
type: 'function',
|
|
756
|
+
}));
|
|
757
|
+
const { messages } = getChatNodeMessages(inputs);
|
|
758
|
+
const isReasoningModel = finalModel.startsWith('o1') || finalModel.startsWith('o3');
|
|
759
|
+
const completionMessages = await Promise.all(messages.map((message) => chatMessageToOpenAIChatCompletionMessage(message, { isReasoningModel })));
|
|
760
|
+
let { maxTokens } = data;
|
|
761
|
+
const openaiModel = {
|
|
762
|
+
...(openaiModels[model] ?? {
|
|
763
|
+
maxTokens: data.overrideMaxTokens ?? 8192,
|
|
764
|
+
cost: {
|
|
765
|
+
completion: 0,
|
|
766
|
+
prompt: 0,
|
|
767
|
+
},
|
|
768
|
+
displayName: 'Custom Model',
|
|
769
|
+
}),
|
|
770
|
+
};
|
|
771
|
+
if (data.overrideMaxTokens) {
|
|
772
|
+
openaiModel.maxTokens = data.overrideMaxTokens;
|
|
773
|
+
}
|
|
774
|
+
const isMultiResponse = data.useNumberOfChoicesInput || (data.numberOfChoices ?? 1) > 1;
|
|
775
|
+
// Resolve to final endpoint if configured in ProcessContext
|
|
776
|
+
const configuredEndpoint = endpoint || context.settings.openAiEndpoint || DEFAULT_CHAT_ENDPOINT;
|
|
777
|
+
const resolvedEndpointAndHeaders = context.getChatNodeEndpoint
|
|
778
|
+
? await context.getChatNodeEndpoint(configuredEndpoint, finalModel)
|
|
779
|
+
: {
|
|
780
|
+
endpoint: configuredEndpoint,
|
|
781
|
+
headers: {},
|
|
782
|
+
};
|
|
783
|
+
const allAdditionalHeaders = cleanHeaders({
|
|
784
|
+
...context.settings.chatNodeHeaders,
|
|
785
|
+
...additionalHeaders,
|
|
786
|
+
...resolvedEndpointAndHeaders.headers,
|
|
787
|
+
});
|
|
788
|
+
let inputTokenCount = 0;
|
|
789
|
+
const tokenizerInfo = {
|
|
790
|
+
node,
|
|
791
|
+
model: finalModel,
|
|
792
|
+
endpoint: resolvedEndpointAndHeaders.endpoint,
|
|
793
|
+
};
|
|
794
|
+
if (!data.useServerTokenCalculation) {
|
|
795
|
+
inputTokenCount = await context.tokenizer.getTokenCountForMessages(messages, functions, tokenizerInfo);
|
|
796
|
+
if (inputTokenCount >= openaiModel.maxTokens) {
|
|
797
|
+
throw new Error(`The model ${model} can only handle ${openaiModel.maxTokens} tokens, but ${inputTokenCount} were provided in the prompts alone.`);
|
|
798
|
+
}
|
|
799
|
+
if (inputTokenCount + maxTokens > openaiModel.maxTokens) {
|
|
800
|
+
const message = `The model can only handle a maximum of ${openaiModel.maxTokens} tokens, but the prompts and max tokens together exceed this limit. The max tokens has been reduced to ${openaiModel.maxTokens - inputTokenCount}.`;
|
|
801
|
+
addWarning(output, message);
|
|
802
|
+
maxTokens = Math.floor((openaiModel.maxTokens - inputTokenCount) * 0.95); // reduce max tokens by 5% to be safe, calculation is a little wrong.
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
const predictionObject = predictedOutput
|
|
806
|
+
? predictedOutput.length === 1
|
|
807
|
+
? { type: 'content', content: predictedOutput[0] }
|
|
808
|
+
: { type: 'content', content: predictedOutput.map((part) => ({ type: 'text', text: part })) }
|
|
809
|
+
: undefined;
|
|
810
|
+
const voice = getInputOrData(data, inputs, 'audioVoice');
|
|
811
|
+
let modalities = [];
|
|
812
|
+
if (data.modalitiesIncludeText) {
|
|
813
|
+
modalities.push('text');
|
|
814
|
+
}
|
|
815
|
+
if (data.modalitiesIncludeAudio) {
|
|
816
|
+
modalities.push('audio');
|
|
817
|
+
if (!voice) {
|
|
818
|
+
throw new Error('Audio voice must be specified if audio is enabled.');
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
// Errors happen if modalities isn't supported, so omit it if it's empty
|
|
822
|
+
if (modalities.length === 0) {
|
|
823
|
+
modalities = undefined;
|
|
824
|
+
}
|
|
825
|
+
const audio = modalities?.includes('audio')
|
|
826
|
+
? {
|
|
827
|
+
voice,
|
|
828
|
+
format: getInputOrData(data, inputs, 'audioFormat') ??
|
|
829
|
+
'wav',
|
|
830
|
+
}
|
|
831
|
+
: undefined;
|
|
832
|
+
const reasoningEffort = getInputOrData(data, inputs, 'reasoningEffort');
|
|
833
|
+
const supported = openaiModels[finalModel]?.supported ??
|
|
834
|
+
defaultOpenaiSupported;
|
|
835
|
+
try {
|
|
836
|
+
return await retry(async () => {
|
|
837
|
+
const options = {
|
|
838
|
+
messages: completionMessages,
|
|
839
|
+
model: finalModel,
|
|
840
|
+
top_p: useTopP ? topP : undefined,
|
|
841
|
+
n: numberOfChoices,
|
|
842
|
+
frequency_penalty: frequencyPenalty,
|
|
843
|
+
presence_penalty: presencePenalty,
|
|
844
|
+
stop: stop || undefined,
|
|
845
|
+
tools: tools.length > 0 ? tools : undefined,
|
|
846
|
+
endpoint: resolvedEndpointAndHeaders.endpoint,
|
|
847
|
+
seed,
|
|
848
|
+
response_format: openaiResponseFormat,
|
|
849
|
+
tool_choice: toolChoice,
|
|
850
|
+
parallel_tool_calls: tools.length > 0 && supported.parallelFunctionCalls ? parallelFunctionCalling : undefined,
|
|
851
|
+
prediction: predictionObject,
|
|
852
|
+
modalities,
|
|
853
|
+
audio,
|
|
854
|
+
reasoning_effort: reasoningEffort || undefined,
|
|
855
|
+
...additionalParameters,
|
|
856
|
+
};
|
|
857
|
+
const isO1Beta = finalModel.startsWith('o1-preview') || finalModel.startsWith('o1-mini');
|
|
858
|
+
if (isReasoningModel) {
|
|
859
|
+
options.max_completion_tokens = maxTokens;
|
|
860
|
+
}
|
|
861
|
+
else {
|
|
862
|
+
options.temperature = useTopP ? undefined : temperature; // Not supported in o1-preview
|
|
863
|
+
options.max_tokens = maxTokens;
|
|
864
|
+
}
|
|
865
|
+
const cacheKey = JSON.stringify(options);
|
|
866
|
+
if (data.cache) {
|
|
867
|
+
const cached = cache.get(cacheKey);
|
|
868
|
+
if (cached) {
|
|
869
|
+
return cached;
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
const startTime = Date.now();
|
|
873
|
+
// Non-streaming APIs
|
|
874
|
+
if (isO1Beta || audio) {
|
|
875
|
+
const response = await chatCompletions({
|
|
876
|
+
auth: {
|
|
877
|
+
apiKey: context.settings.openAiKey ?? '',
|
|
878
|
+
organization: context.settings.openAiOrganization,
|
|
879
|
+
},
|
|
880
|
+
headers: allAdditionalHeaders,
|
|
881
|
+
signal: context.signal,
|
|
882
|
+
timeout: context.settings.chatNodeTimeout,
|
|
883
|
+
...options,
|
|
884
|
+
});
|
|
885
|
+
if ('error' in response) {
|
|
886
|
+
throw new OpenAIError(400, response.error);
|
|
887
|
+
}
|
|
888
|
+
if (isMultiResponse) {
|
|
889
|
+
output['response'] = {
|
|
890
|
+
type: 'string[]',
|
|
891
|
+
value: response.choices.map((c) => c.message.content),
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
output['response'] = {
|
|
896
|
+
type: 'string',
|
|
897
|
+
value: response.choices[0].message.content ?? '',
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
if (!isMultiResponse) {
|
|
901
|
+
output['all-messages'] = {
|
|
902
|
+
type: 'chat-message[]',
|
|
903
|
+
value: [
|
|
904
|
+
...messages,
|
|
905
|
+
{
|
|
906
|
+
type: 'assistant',
|
|
907
|
+
message: response.choices[0].message.content ?? '',
|
|
908
|
+
function_calls: undefined,
|
|
909
|
+
isCacheBreakpoint: false,
|
|
910
|
+
function_call: undefined,
|
|
911
|
+
},
|
|
912
|
+
],
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
if (modalities?.includes('audio')) {
|
|
916
|
+
const audioData = response.choices[0].message.audio;
|
|
917
|
+
output['audio'] = {
|
|
918
|
+
type: 'audio',
|
|
919
|
+
value: {
|
|
920
|
+
data: base64ToUint8Array(audioData.data),
|
|
921
|
+
mediaType: audioFormatToMediaType(audio.format),
|
|
922
|
+
},
|
|
923
|
+
};
|
|
924
|
+
output['audioTranscript'] = {
|
|
925
|
+
type: 'string',
|
|
926
|
+
value: response.choices[0].message.audio.transcript,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
output['duration'] = { type: 'number', value: Date.now() - startTime };
|
|
930
|
+
if (response.usage) {
|
|
931
|
+
output['usage'] = {
|
|
932
|
+
type: 'object',
|
|
933
|
+
value: response.usage,
|
|
934
|
+
};
|
|
935
|
+
const costs = finalModel in openaiModels ? openaiModels[finalModel].cost : undefined;
|
|
936
|
+
const promptCostPerThousand = costs?.prompt ?? 0;
|
|
937
|
+
const completionCostPerThousand = costs?.completion ?? 0;
|
|
938
|
+
const audioPromptCostPerThousand = costs
|
|
939
|
+
? 'audioPrompt' in costs
|
|
940
|
+
? costs.audioPrompt
|
|
941
|
+
: 0
|
|
942
|
+
: 0;
|
|
943
|
+
const audioCompletionCostPerThousand = costs
|
|
944
|
+
? 'audioCompletion' in costs
|
|
945
|
+
? costs.audioCompletion
|
|
946
|
+
: 0
|
|
947
|
+
: 0;
|
|
948
|
+
const promptCost = getCostForTokens(response.usage.prompt_tokens_details.text_tokens, promptCostPerThousand);
|
|
949
|
+
const completionCost = getCostForTokens(response.usage.completion_tokens_details.text_tokens, completionCostPerThousand);
|
|
950
|
+
const audioPromptCost = getCostForTokens(response.usage.prompt_tokens_details.audio_tokens, audioPromptCostPerThousand);
|
|
951
|
+
const audioCompletionCost = getCostForTokens(response.usage.completion_tokens_details.audio_tokens, audioCompletionCostPerThousand);
|
|
952
|
+
output['cost'] = {
|
|
953
|
+
type: 'number',
|
|
954
|
+
value: promptCost + completionCost + audioPromptCost + audioCompletionCost,
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
Object.freeze(output);
|
|
958
|
+
cache.set(cacheKey, output);
|
|
959
|
+
return output;
|
|
960
|
+
}
|
|
961
|
+
const chunks = streamChatCompletions({
|
|
962
|
+
auth: {
|
|
963
|
+
apiKey: context.settings.openAiKey ?? '',
|
|
964
|
+
organization: context.settings.openAiOrganization,
|
|
965
|
+
},
|
|
966
|
+
headers: allAdditionalHeaders,
|
|
967
|
+
signal: context.signal,
|
|
968
|
+
timeout: context.settings.chatNodeTimeout,
|
|
969
|
+
...options,
|
|
970
|
+
});
|
|
971
|
+
const responseChoicesParts = [];
|
|
972
|
+
// First array is the function calls per choice, inner array is the functions calls inside the choice
|
|
973
|
+
const functionCalls = [];
|
|
974
|
+
let usage;
|
|
975
|
+
let throttleLastCalledTime = Date.now();
|
|
976
|
+
const onPartialOutput = (output) => {
|
|
977
|
+
const now = Date.now();
|
|
978
|
+
if (now - throttleLastCalledTime > (context.settings.throttleChatNode ?? 100)) {
|
|
979
|
+
context.onPartialOutputs?.(output);
|
|
980
|
+
throttleLastCalledTime = now;
|
|
981
|
+
}
|
|
982
|
+
};
|
|
983
|
+
for await (const chunk of chunks) {
|
|
984
|
+
if (chunk.usage) {
|
|
985
|
+
usage = chunk.usage;
|
|
986
|
+
}
|
|
987
|
+
if (!chunk.choices) {
|
|
988
|
+
// Could be error for some reason 🤷♂️ but ignoring has worked for me so far.
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
for (const { delta, index } of chunk.choices) {
|
|
992
|
+
if (delta.content != null) {
|
|
993
|
+
responseChoicesParts[index] ??= [];
|
|
994
|
+
responseChoicesParts[index].push(delta.content);
|
|
995
|
+
}
|
|
996
|
+
if (delta.tool_calls) {
|
|
997
|
+
// Are we sure that tool_calls will always be full and not a bunch of deltas?
|
|
998
|
+
functionCalls[index] ??= [];
|
|
999
|
+
for (const toolCall of delta.tool_calls) {
|
|
1000
|
+
functionCalls[index][toolCall.index] ??= {
|
|
1001
|
+
type: 'function',
|
|
1002
|
+
arguments: '',
|
|
1003
|
+
lastParsedArguments: undefined,
|
|
1004
|
+
name: '',
|
|
1005
|
+
id: '',
|
|
1006
|
+
};
|
|
1007
|
+
if (toolCall.id) {
|
|
1008
|
+
functionCalls[index][toolCall.index].id = toolCall.id;
|
|
1009
|
+
}
|
|
1010
|
+
if (toolCall.function.name) {
|
|
1011
|
+
functionCalls[index][toolCall.index].name += toolCall.function.name;
|
|
1012
|
+
}
|
|
1013
|
+
if (toolCall.function.arguments) {
|
|
1014
|
+
functionCalls[index][toolCall.index].arguments += toolCall.function.arguments;
|
|
1015
|
+
try {
|
|
1016
|
+
functionCalls[index][toolCall.index].lastParsedArguments = JSON.parse(functionCalls[index][toolCall.index].arguments);
|
|
1017
|
+
}
|
|
1018
|
+
catch (error) {
|
|
1019
|
+
// Ignore
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
if (isMultiResponse) {
|
|
1026
|
+
output['response'] = {
|
|
1027
|
+
type: 'string[]',
|
|
1028
|
+
value: responseChoicesParts.map((parts) => parts.join('')),
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
else {
|
|
1032
|
+
output['response'] = {
|
|
1033
|
+
type: 'string',
|
|
1034
|
+
value: responseChoicesParts[0]?.join('') ?? '',
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
if (functionCalls.length > 0) {
|
|
1038
|
+
if (isMultiResponse) {
|
|
1039
|
+
output['function-call'] = {
|
|
1040
|
+
type: 'object[]',
|
|
1041
|
+
value: functionCalls.map((functionCalls) => ({
|
|
1042
|
+
name: functionCalls[0]?.name,
|
|
1043
|
+
arguments: functionCalls[0]?.lastParsedArguments,
|
|
1044
|
+
id: functionCalls[0]?.id,
|
|
1045
|
+
})),
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
else {
|
|
1049
|
+
if (data.parallelFunctionCalling) {
|
|
1050
|
+
output['function-calls'] = {
|
|
1051
|
+
type: 'object[]',
|
|
1052
|
+
value: functionCalls[0].map((functionCall) => ({
|
|
1053
|
+
name: functionCall.name,
|
|
1054
|
+
arguments: functionCall.lastParsedArguments,
|
|
1055
|
+
id: functionCall.id,
|
|
1056
|
+
})),
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
output['function-call'] = {
|
|
1061
|
+
type: 'object',
|
|
1062
|
+
value: {
|
|
1063
|
+
name: functionCalls[0][0]?.name,
|
|
1064
|
+
arguments: functionCalls[0][0]?.lastParsedArguments,
|
|
1065
|
+
id: functionCalls[0][0]?.id,
|
|
1066
|
+
},
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
onPartialOutput(output);
|
|
1072
|
+
}
|
|
1073
|
+
// Call one last time manually to ensure the last output is sent
|
|
1074
|
+
context.onPartialOutputs?.(output);
|
|
1075
|
+
if (!isMultiResponse) {
|
|
1076
|
+
output['all-messages'] = {
|
|
1077
|
+
type: 'chat-message[]',
|
|
1078
|
+
value: [
|
|
1079
|
+
...messages,
|
|
1080
|
+
{
|
|
1081
|
+
type: 'assistant',
|
|
1082
|
+
message: responseChoicesParts[0]?.join('') ?? '',
|
|
1083
|
+
function_call: functionCalls[0]
|
|
1084
|
+
? {
|
|
1085
|
+
name: functionCalls[0][0].name,
|
|
1086
|
+
arguments: functionCalls[0][0].arguments, // Needs the stringified one here in chat list
|
|
1087
|
+
id: functionCalls[0][0].id,
|
|
1088
|
+
}
|
|
1089
|
+
: undefined,
|
|
1090
|
+
function_calls: functionCalls[0]
|
|
1091
|
+
? functionCalls[0].map((fc) => ({
|
|
1092
|
+
name: fc.name,
|
|
1093
|
+
arguments: fc.arguments,
|
|
1094
|
+
id: fc.id,
|
|
1095
|
+
}))
|
|
1096
|
+
: undefined,
|
|
1097
|
+
},
|
|
1098
|
+
],
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
const endTime = Date.now();
|
|
1102
|
+
if (responseChoicesParts.length === 0 && functionCalls.length === 0) {
|
|
1103
|
+
throw new Error('No response from OpenAI');
|
|
1104
|
+
}
|
|
1105
|
+
let outputTokenCount = 0;
|
|
1106
|
+
if (usage) {
|
|
1107
|
+
inputTokenCount = usage.prompt_tokens;
|
|
1108
|
+
outputTokenCount = usage.completion_tokens;
|
|
1109
|
+
}
|
|
1110
|
+
output['in-messages'] = { type: 'chat-message[]', value: messages };
|
|
1111
|
+
output['requestTokens'] = { type: 'number', value: inputTokenCount * (numberOfChoices ?? 1) };
|
|
1112
|
+
if (!data.useServerTokenCalculation) {
|
|
1113
|
+
let responseTokenCount = 0;
|
|
1114
|
+
for (const choiceParts of responseChoicesParts) {
|
|
1115
|
+
responseTokenCount += await context.tokenizer.getTokenCountForString(choiceParts.join(), tokenizerInfo);
|
|
1116
|
+
}
|
|
1117
|
+
outputTokenCount = responseTokenCount;
|
|
1118
|
+
}
|
|
1119
|
+
output['responseTokens'] = { type: 'number', value: outputTokenCount };
|
|
1120
|
+
const outputTokensForCostCalculation = usage?.completion_tokens_details
|
|
1121
|
+
? usage.completion_tokens_details.rejected_prediction_tokens > 0
|
|
1122
|
+
? usage.completion_tokens_details.rejected_prediction_tokens
|
|
1123
|
+
: usage.completion_tokens
|
|
1124
|
+
: outputTokenCount;
|
|
1125
|
+
const promptCostPerThousand = model in openaiModels ? openaiModels[model].cost.prompt : 0;
|
|
1126
|
+
const completionCostPerThousand = model in openaiModels ? openaiModels[model].cost.completion : 0;
|
|
1127
|
+
const promptCost = getCostForTokens(inputTokenCount, promptCostPerThousand);
|
|
1128
|
+
const completionCost = getCostForTokens(outputTokensForCostCalculation, completionCostPerThousand);
|
|
1129
|
+
const cost = promptCost + completionCost;
|
|
1130
|
+
if (usage) {
|
|
1131
|
+
output['usage'] = {
|
|
1132
|
+
type: 'object',
|
|
1133
|
+
value: {
|
|
1134
|
+
...usage,
|
|
1135
|
+
prompt_cost: promptCost,
|
|
1136
|
+
completion_cost: completionCost,
|
|
1137
|
+
total_cost: cost,
|
|
1138
|
+
},
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
output['usage'] = {
|
|
1143
|
+
type: 'object',
|
|
1144
|
+
value: {
|
|
1145
|
+
prompt_tokens: inputTokenCount,
|
|
1146
|
+
completion_tokens: outputTokenCount,
|
|
1147
|
+
},
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
output['cost'] = { type: 'number', value: cost };
|
|
1151
|
+
output['__hidden_token_count'] = { type: 'number', value: inputTokenCount + outputTokenCount };
|
|
1152
|
+
const duration = endTime - startTime;
|
|
1153
|
+
output['duration'] = { type: 'number', value: duration };
|
|
1154
|
+
Object.freeze(output);
|
|
1155
|
+
cache.set(cacheKey, output);
|
|
1156
|
+
return output;
|
|
1157
|
+
}, {
|
|
1158
|
+
forever: true,
|
|
1159
|
+
retries: 10000,
|
|
1160
|
+
maxRetryTime: 1000 * 60 * 5,
|
|
1161
|
+
factor: 2.5,
|
|
1162
|
+
minTimeout: 500,
|
|
1163
|
+
maxTimeout: 5000,
|
|
1164
|
+
randomize: true,
|
|
1165
|
+
signal: context.signal,
|
|
1166
|
+
onFailedAttempt(originalError) {
|
|
1167
|
+
let err = originalError;
|
|
1168
|
+
if (originalError.toString().includes('fetch failed') && originalError.cause) {
|
|
1169
|
+
const cause = getError(originalError.cause) instanceof AggregateError
|
|
1170
|
+
? originalError.cause.errors[0]
|
|
1171
|
+
: getError(originalError.cause);
|
|
1172
|
+
err = cause;
|
|
1173
|
+
}
|
|
1174
|
+
if (context.signal.aborted) {
|
|
1175
|
+
throw new Error('Aborted');
|
|
1176
|
+
}
|
|
1177
|
+
context.trace(`ChatNode failed, retrying: ${err.toString()}`);
|
|
1178
|
+
const { retriesLeft } = err;
|
|
1179
|
+
// Retry network errors
|
|
1180
|
+
if (err.toString().includes('terminated') ||
|
|
1181
|
+
originalError.toString().includes('terminated') ||
|
|
1182
|
+
err.toString().includes('fetch failed')) {
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
if (!(err instanceof OpenAIError)) {
|
|
1186
|
+
if ('code' in err) {
|
|
1187
|
+
throw err;
|
|
1188
|
+
}
|
|
1189
|
+
return; // Just retry?
|
|
1190
|
+
}
|
|
1191
|
+
if (err.status === 429) {
|
|
1192
|
+
if (retriesLeft) {
|
|
1193
|
+
context.onPartialOutputs?.({
|
|
1194
|
+
['response']: {
|
|
1195
|
+
type: 'string',
|
|
1196
|
+
value: 'OpenAI API rate limit exceeded, retrying...',
|
|
1197
|
+
},
|
|
1198
|
+
});
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (err.status === 408) {
|
|
1203
|
+
if (retriesLeft) {
|
|
1204
|
+
context.onPartialOutputs?.({
|
|
1205
|
+
['response']: {
|
|
1206
|
+
type: 'string',
|
|
1207
|
+
value: 'OpenAI API timed out, retrying...',
|
|
1208
|
+
},
|
|
1209
|
+
});
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
// We did something wrong (besides rate limit)
|
|
1214
|
+
if (err.status >= 400 && err.status < 500) {
|
|
1215
|
+
throw new Error(err.message);
|
|
1216
|
+
}
|
|
1217
|
+
},
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
catch (error) {
|
|
1221
|
+
context.trace(getError(error).stack ?? 'Missing stack');
|
|
1222
|
+
throw new Error(`Error processing ChatNode: ${error.message}`, { cause: error });
|
|
1223
|
+
}
|
|
1224
|
+
},
|
|
1225
|
+
};
|
|
1226
|
+
export function getChatNodeMessages(inputs) {
|
|
1227
|
+
const prompt = inputs['prompt'];
|
|
1228
|
+
let messages = match(prompt)
|
|
1229
|
+
.with({ type: 'chat-message' }, (p) => [p.value])
|
|
1230
|
+
.with({ type: 'chat-message[]' }, (p) => p.value)
|
|
1231
|
+
.with({ type: 'string' }, (p) => [{ type: 'user', message: p.value }])
|
|
1232
|
+
.with({ type: 'string[]' }, (p) => p.value.map((v) => ({ type: 'user', message: v })))
|
|
1233
|
+
.otherwise((p) => {
|
|
1234
|
+
if (!p) {
|
|
1235
|
+
return [];
|
|
1236
|
+
}
|
|
1237
|
+
if (isArrayDataValue(p)) {
|
|
1238
|
+
const stringValues = p.value.map((v) => coerceType({
|
|
1239
|
+
type: getScalarTypeOf(p.type),
|
|
1240
|
+
value: v,
|
|
1241
|
+
}, 'string'));
|
|
1242
|
+
return stringValues.filter((v) => v != null).map((v) => ({ type: 'user', message: v }));
|
|
1243
|
+
}
|
|
1244
|
+
const coercedMessage = coerceTypeOptional(p, 'chat-message');
|
|
1245
|
+
if (coercedMessage != null) {
|
|
1246
|
+
return [coercedMessage];
|
|
1247
|
+
}
|
|
1248
|
+
const coercedString = coerceTypeOptional(p, 'string');
|
|
1249
|
+
return coercedString != null ? [{ type: 'user', message: coerceType(p, 'string') }] : [];
|
|
1250
|
+
});
|
|
1251
|
+
const systemPrompt = inputs['systemPrompt'];
|
|
1252
|
+
if (systemPrompt) {
|
|
1253
|
+
if (messages.length > 0 && messages.at(0).type === 'system') {
|
|
1254
|
+
// Delete the first system message if it's already there
|
|
1255
|
+
messages.splice(0, 1);
|
|
1256
|
+
}
|
|
1257
|
+
messages = [{ type: 'system', message: coerceType(systemPrompt, 'string') }, ...messages];
|
|
1258
|
+
}
|
|
1259
|
+
return { messages, systemPrompt };
|
|
1260
|
+
}
|
|
1261
|
+
export function getCostForTokens(tokenCount, costPerThousand) {
|
|
1262
|
+
return (tokenCount / 1000) * costPerThousand;
|
|
1263
|
+
}
|
|
1264
|
+
function audioFormatToMediaType(format) {
|
|
1265
|
+
switch (format) {
|
|
1266
|
+
case 'wav':
|
|
1267
|
+
return 'audio/wav';
|
|
1268
|
+
case 'mp3':
|
|
1269
|
+
return 'audio/mpeg';
|
|
1270
|
+
case 'flac':
|
|
1271
|
+
return 'audio/flac';
|
|
1272
|
+
case 'opus':
|
|
1273
|
+
return 'audio/opus';
|
|
1274
|
+
case 'pcm16':
|
|
1275
|
+
return 'audio/wav';
|
|
1276
|
+
}
|
|
1277
|
+
}
|