@alpic80/rivet-core 1.19.1-aidon.3 → 1.24.0-aidon.1

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.
Files changed (105) hide show
  1. package/README.md +4 -0
  2. package/dist/cjs/bundle.cjs +3993 -943
  3. package/dist/cjs/bundle.cjs.map +4 -4
  4. package/dist/esm/api/createProcessor.js +8 -17
  5. package/dist/esm/api/looseDataValue.js +16 -0
  6. package/dist/esm/exports.js +2 -0
  7. package/dist/esm/integrations/CodeRunner.js +36 -0
  8. package/dist/esm/integrations/GptTokenizerTokenizer.js +7 -4
  9. package/dist/esm/integrations/openai/OpenAIEmbeddingGenerator.js +1 -1
  10. package/dist/esm/model/DataValue.js +14 -2
  11. package/dist/esm/model/GraphProcessor.js +275 -104
  12. package/dist/esm/model/NodeBase.js +11 -1
  13. package/dist/esm/model/NodeImpl.js +8 -0
  14. package/dist/esm/model/Nodes.js +28 -4
  15. package/dist/esm/model/ProjectReferenceLoader.js +1 -0
  16. package/dist/esm/model/nodes/AssembleMessageNode.js +12 -2
  17. package/dist/esm/model/nodes/AssemblePromptNode.js +22 -0
  18. package/dist/esm/model/nodes/CallGraphNode.js +3 -4
  19. package/dist/esm/model/nodes/ChatLoopNode.js +150 -0
  20. package/dist/esm/model/nodes/ChatNode.js +7 -934
  21. package/dist/esm/model/nodes/ChatNodeBase.js +1275 -0
  22. package/dist/esm/model/nodes/ChunkNode.js +2 -2
  23. package/dist/esm/model/nodes/CodeNode.js +40 -4
  24. package/dist/esm/model/nodes/CronNode.js +248 -0
  25. package/dist/esm/model/nodes/DelegateFunctionCallNode.js +37 -12
  26. package/dist/esm/model/nodes/DestructureNode.js +1 -1
  27. package/dist/esm/model/nodes/DocumentNode.js +183 -0
  28. package/dist/esm/model/nodes/ExtractJsonNode.js +4 -4
  29. package/dist/esm/model/nodes/ExtractRegexNode.js +10 -11
  30. package/dist/esm/model/nodes/GetEmbeddingNode.js +1 -1
  31. package/dist/esm/model/nodes/HttpCallNode.js +3 -1
  32. package/dist/esm/model/nodes/IfNode.js +5 -0
  33. package/dist/esm/model/nodes/LoopUntilNode.js +214 -0
  34. package/dist/esm/model/nodes/PromptNode.js +29 -6
  35. package/dist/esm/model/nodes/ReadAllFilesNode.js +210 -0
  36. package/dist/esm/model/nodes/ReadDirectoryNode.js +31 -25
  37. package/dist/esm/model/nodes/ReferencedGraphAliasNode.js +199 -0
  38. package/dist/esm/model/nodes/TextNode.js +9 -4
  39. package/dist/esm/model/nodes/ToMarkdownTableNode.js +119 -0
  40. package/dist/esm/model/nodes/ToTreeNode.js +133 -0
  41. package/dist/esm/model/nodes/{GptFunctionNode.js → ToolNode.js} +10 -10
  42. package/dist/esm/model/nodes/UserInputNode.js +10 -12
  43. package/dist/esm/plugins/aidon/nodes/ChatAidonNode.js +2 -2
  44. package/dist/esm/plugins/anthropic/anthropic.js +29 -10
  45. package/dist/esm/plugins/anthropic/fetchEventSource.js +3 -2
  46. package/dist/esm/plugins/anthropic/nodes/ChatAnthropicNode.js +267 -147
  47. package/dist/esm/plugins/anthropic/plugin.js +9 -1
  48. package/dist/esm/plugins/gentrace/plugin.js +6 -6
  49. package/dist/esm/plugins/google/google.js +113 -5
  50. package/dist/esm/plugins/google/nodes/ChatGoogleNode.js +211 -54
  51. package/dist/esm/plugins/google/plugin.js +13 -6
  52. package/dist/esm/plugins/openai/nodes/RunThreadNode.js +2 -2
  53. package/dist/esm/recording/ExecutionRecorder.js +5 -1
  54. package/dist/esm/utils/chatMessageToOpenAIChatCompletionMessage.js +15 -2
  55. package/dist/esm/utils/coerceType.js +1 -1
  56. package/dist/esm/utils/fetchEventSource.js +1 -1
  57. package/dist/esm/utils/interpolation.js +108 -3
  58. package/dist/esm/utils/openai.js +106 -50
  59. package/dist/esm/utils/paths.js +80 -0
  60. package/dist/esm/utils/serialization/serialization_v4.js +5 -0
  61. package/dist/types/api/createProcessor.d.ts +11 -5
  62. package/dist/types/api/looseDataValue.d.ts +4 -0
  63. package/dist/types/api/streaming.d.ts +1 -1
  64. package/dist/types/exports.d.ts +2 -0
  65. package/dist/types/integrations/CodeRunner.d.ts +18 -0
  66. package/dist/types/model/DataValue.d.ts +29 -6
  67. package/dist/types/model/EditorDefinition.d.ts +6 -1
  68. package/dist/types/model/GraphProcessor.d.ts +14 -7
  69. package/dist/types/model/NodeBase.d.ts +4 -0
  70. package/dist/types/model/NodeImpl.d.ts +5 -4
  71. package/dist/types/model/Nodes.d.ts +12 -4
  72. package/dist/types/model/ProcessContext.d.ts +16 -1
  73. package/dist/types/model/Project.d.ts +19 -7
  74. package/dist/types/model/ProjectReferenceLoader.d.ts +5 -0
  75. package/dist/types/model/RivetPlugin.d.ts +6 -0
  76. package/dist/types/model/RivetUIContext.d.ts +5 -1
  77. package/dist/types/model/Settings.d.ts +1 -0
  78. package/dist/types/model/nodes/AssemblePromptNode.d.ts +4 -1
  79. package/dist/types/model/nodes/ChatLoopNode.d.ts +21 -0
  80. package/dist/types/model/nodes/ChatNode.d.ts +2 -62
  81. package/dist/types/model/nodes/ChatNodeBase.d.ts +85 -0
  82. package/dist/types/model/nodes/CodeNode.d.ts +8 -2
  83. package/dist/types/model/nodes/CronNode.d.ts +34 -0
  84. package/dist/types/model/nodes/DelegateFunctionCallNode.d.ts +1 -0
  85. package/dist/types/model/nodes/DocumentNode.d.ts +28 -0
  86. package/dist/types/model/nodes/LoopUntilNode.d.ts +32 -0
  87. package/dist/types/model/nodes/PromptNode.d.ts +2 -0
  88. package/dist/types/model/nodes/ReadAllFilesNode.d.ts +30 -0
  89. package/dist/types/model/nodes/ReadDirectoryNode.d.ts +1 -1
  90. package/dist/types/model/nodes/ReferencedGraphAliasNode.d.ts +31 -0
  91. package/dist/types/model/nodes/ToMarkdownTableNode.d.ts +19 -0
  92. package/dist/types/model/nodes/ToTreeNode.d.ts +21 -0
  93. package/dist/types/model/nodes/UserInputNode.d.ts +2 -3
  94. package/dist/types/plugins/anthropic/anthropic.d.ts +94 -13
  95. package/dist/types/plugins/anthropic/nodes/ChatAnthropicNode.d.ts +7 -2
  96. package/dist/types/plugins/google/google.d.ts +93 -18
  97. package/dist/types/plugins/google/nodes/ChatGoogleNode.d.ts +3 -2
  98. package/dist/types/recording/RecordedEvents.d.ts +2 -0
  99. package/dist/types/utils/base64.d.ts +1 -1
  100. package/dist/types/utils/chatMessageToOpenAIChatCompletionMessage.d.ts +3 -1
  101. package/dist/types/utils/interpolation.d.ts +3 -0
  102. package/dist/types/utils/openai.d.ts +127 -21
  103. package/dist/types/utils/paths.d.ts +8 -0
  104. package/package.json +15 -11
  105. /package/dist/types/model/nodes/{GptFunctionNode.d.ts → ToolNode.d.ts} +0 -0
@@ -1,22 +1,11 @@
1
1
  import {} from '../NodeBase.js';
2
2
  import { nanoid } from 'nanoid/non-secure';
3
3
  import { NodeImpl } from '../NodeImpl.js';
4
- import { getScalarTypeOf, isArrayDataValue } from '../DataValue.js';
5
- import { addWarning } from '../../utils/outputs.js';
6
- import { OpenAIError, openAiModelOptions, openaiModels, streamChatCompletions, } from '../../utils/openai.js';
7
- import retry from 'p-retry';
8
- import { match } from 'ts-pattern';
9
- import { coerceType, coerceTypeOptional } from '../../utils/coerceType.js';
10
4
  import {} from '../ProcessContext.js';
11
5
  import {} from '../../index.js';
12
6
  import { dedent } from 'ts-dedent';
13
- import { getInputOrData, cleanHeaders } from '../../utils/inputs.js';
14
- import { getError } from '../../utils/errors.js';
15
7
  import { nodeDefinition } from '../NodeDefinition.js';
16
- import { DEFAULT_CHAT_ENDPOINT } from '../../utils/defaults.js';
17
- import { chatMessageToOpenAIChatCompletionMessage } from '../../utils/chatMessageToOpenAIChatCompletionMessage.js';
18
- // Temporary
19
- const cache = new Map();
8
+ import { ChatNodeBase } from './ChatNodeBase.js';
20
9
  export class ChatNodeImpl extends NodeImpl {
21
10
  static create() {
22
11
  const chartNode = {
@@ -28,280 +17,15 @@ export class ChatNodeImpl extends NodeImpl {
28
17
  y: 0,
29
18
  width: 200,
30
19
  },
31
- data: {
32
- model: 'gpt-4o-mini',
33
- useModelInput: false,
34
- temperature: 0.5,
35
- useTemperatureInput: false,
36
- top_p: 1,
37
- useTopPInput: false,
38
- useTopP: false,
39
- useUseTopPInput: false,
40
- maxTokens: 1024,
41
- useMaxTokensInput: false,
42
- useStop: false,
43
- stop: '',
44
- useStopInput: false,
45
- presencePenalty: undefined,
46
- usePresencePenaltyInput: false,
47
- frequencyPenalty: undefined,
48
- useFrequencyPenaltyInput: false,
49
- user: undefined,
50
- useUserInput: false,
51
- enableFunctionUse: false,
52
- cache: false,
53
- useAsGraphPartialOutput: true,
54
- parallelFunctionCalling: true,
55
- additionalParameters: [],
56
- useAdditionalParametersInput: false,
57
- },
20
+ data: ChatNodeBase.defaultData(),
58
21
  };
59
22
  return chartNode;
60
23
  }
61
24
  getInputDefinitions() {
62
- const inputs = [];
63
- if (this.data.useEndpointInput) {
64
- inputs.push({
65
- dataType: 'string',
66
- id: 'endpoint',
67
- title: 'Endpoint',
68
- 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',
69
- });
70
- }
71
- inputs.push({
72
- id: 'systemPrompt',
73
- title: 'System Prompt',
74
- dataType: 'string',
75
- required: false,
76
- description: 'The system prompt to send to the model.',
77
- coerced: true,
78
- });
79
- if (this.data.useModelInput) {
80
- inputs.push({
81
- id: 'model',
82
- title: 'Model',
83
- dataType: 'string',
84
- required: false,
85
- description: 'The model to use for the chat.',
86
- });
87
- }
88
- if (this.data.useTemperatureInput) {
89
- inputs.push({
90
- dataType: 'number',
91
- id: 'temperature',
92
- title: 'Temperature',
93
- 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.',
94
- });
95
- }
96
- if (this.data.useTopPInput) {
97
- inputs.push({
98
- dataType: 'number',
99
- id: 'top_p',
100
- title: 'Top P',
101
- 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.',
102
- });
103
- }
104
- if (this.data.useUseTopPInput) {
105
- inputs.push({
106
- dataType: 'boolean',
107
- id: 'useTopP',
108
- title: 'Use Top P',
109
- description: 'Whether to use top p sampling, or temperature sampling.',
110
- });
111
- }
112
- if (this.data.useMaxTokensInput) {
113
- inputs.push({
114
- dataType: 'number',
115
- id: 'maxTokens',
116
- title: 'Max Tokens',
117
- description: 'The maximum number of tokens to generate in the chat completion.',
118
- });
119
- }
120
- if (this.data.useStopInput) {
121
- inputs.push({
122
- dataType: 'string',
123
- id: 'stop',
124
- title: 'Stop',
125
- description: 'A sequence where the API will stop generating further tokens.',
126
- });
127
- }
128
- if (this.data.usePresencePenaltyInput) {
129
- inputs.push({
130
- dataType: 'number',
131
- id: 'presencePenalty',
132
- title: 'Presence Penalty',
133
- 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.`,
134
- });
135
- }
136
- if (this.data.useFrequencyPenaltyInput) {
137
- inputs.push({
138
- dataType: 'number',
139
- id: 'frequencyPenalty',
140
- title: 'Frequency Penalty',
141
- 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.`,
142
- });
143
- }
144
- if (this.data.useUserInput) {
145
- inputs.push({
146
- dataType: 'string',
147
- id: 'user',
148
- title: 'User',
149
- description: 'A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.',
150
- });
151
- }
152
- if (this.data.useNumberOfChoicesInput) {
153
- inputs.push({
154
- dataType: 'number',
155
- id: 'numberOfChoices',
156
- title: 'Number of Choices',
157
- description: 'If greater than 1, the model will return multiple choices and the response will be an array.',
158
- });
159
- }
160
- if (this.data.useHeadersInput) {
161
- inputs.push({
162
- dataType: 'object',
163
- id: 'headers',
164
- title: 'Headers',
165
- description: 'Additional headers to send to the API.',
166
- });
167
- }
168
- inputs.push({
169
- dataType: ['chat-message', 'chat-message[]'],
170
- id: 'prompt',
171
- title: 'Prompt',
172
- description: 'The prompt message or messages to send to the model.',
173
- coerced: true,
174
- });
175
- if (this.data.enableFunctionUse) {
176
- inputs.push({
177
- dataType: ['gpt-function', 'gpt-function[]'],
178
- id: 'functions',
179
- title: 'Functions',
180
- description: 'Functions to use in the model. To connect multiple functions, use an Array node.',
181
- coerced: false,
182
- });
183
- }
184
- if (this.data.useSeedInput) {
185
- inputs.push({
186
- dataType: 'number',
187
- id: 'seed',
188
- title: 'Seed',
189
- coerced: true,
190
- 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.',
191
- });
192
- }
193
- if (this.data.useToolChoiceInput) {
194
- inputs.push({
195
- dataType: 'string',
196
- id: 'toolChoice',
197
- title: 'Tool Choice',
198
- coerced: true,
199
- 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.',
200
- });
201
- }
202
- if (this.data.useToolChoiceInput || this.data.useToolChoiceFunctionInput) {
203
- inputs.push({
204
- dataType: 'string',
205
- id: 'toolChoiceFunction',
206
- title: 'Tool Choice Function',
207
- coerced: true,
208
- description: 'The name of the function to force the model to call.',
209
- });
210
- }
211
- if (this.data.useResponseFormatInput) {
212
- inputs.push({
213
- dataType: 'string',
214
- id: 'responseFormat',
215
- title: 'Response Format',
216
- coerced: true,
217
- description: 'The format to force the model to reply in.',
218
- });
219
- }
220
- if (this.data.useAdditionalParametersInput) {
221
- inputs.push({
222
- dataType: 'object',
223
- id: 'additionalParameters',
224
- title: 'Additional Parameters',
225
- description: 'Additional chat completion parameters to send to the API.',
226
- });
227
- }
228
- if (this.data.responseFormat === 'json_schema') {
229
- inputs.push({
230
- dataType: 'object',
231
- id: 'responseSchema',
232
- title: 'Response Schema',
233
- description: 'The JSON schema that the response will adhere to (Structured Outputs).',
234
- required: true,
235
- });
236
- if (this.data.useResponseSchemaNameInput) {
237
- inputs.push({
238
- dataType: 'string',
239
- id: 'responseSchemaName',
240
- title: 'Response Schema Name',
241
- description: 'The name of the JSON schema that the response will adhere to (Structured Outputs).',
242
- required: false,
243
- });
244
- }
245
- }
246
- return inputs;
25
+ return ChatNodeBase.getInputDefinitions(this.data);
247
26
  }
248
27
  getOutputDefinitions() {
249
- const outputs = [];
250
- if (this.data.useNumberOfChoicesInput || (this.data.numberOfChoices ?? 1) > 1) {
251
- outputs.push({
252
- dataType: 'string[]',
253
- id: 'response',
254
- title: 'Responses',
255
- description: 'All responses from the model.',
256
- });
257
- }
258
- else {
259
- outputs.push({
260
- dataType: 'string',
261
- id: 'response',
262
- title: 'Response',
263
- description: 'The textual response from the model.',
264
- });
265
- }
266
- if (this.data.enableFunctionUse) {
267
- if (this.data.parallelFunctionCalling) {
268
- outputs.push({
269
- dataType: 'object[]',
270
- id: 'function-calls',
271
- title: 'Function Calls',
272
- description: 'The function calls that were made, if any.',
273
- });
274
- }
275
- else {
276
- outputs.push({
277
- dataType: 'object',
278
- id: 'function-call',
279
- title: 'Function Call',
280
- description: 'The function call that was made, if any.',
281
- });
282
- }
283
- }
284
- outputs.push({
285
- dataType: 'chat-message[]',
286
- id: 'in-messages',
287
- title: 'Messages Sent',
288
- description: 'All messages sent to the model.',
289
- });
290
- if (!(this.data.useNumberOfChoicesInput || (this.data.numberOfChoices ?? 1) > 1)) {
291
- outputs.push({
292
- dataType: 'chat-message[]',
293
- id: 'all-messages',
294
- title: 'All Messages',
295
- description: 'All messages, with the response appended.',
296
- });
297
- }
298
- outputs.push({
299
- dataType: 'number',
300
- id: 'responseTokens',
301
- title: 'Response Tokens',
302
- description: 'The number of tokens in the response from the LLM. For a multi-response, this is the sum.',
303
- });
304
- return outputs;
28
+ return ChatNodeBase.getOutputDefinitions(this.data);
305
29
  }
306
30
  static getUIData() {
307
31
  return {
@@ -318,664 +42,13 @@ export class ChatNodeImpl extends NodeImpl {
318
42
  };
319
43
  }
320
44
  getEditors() {
321
- return [
322
- {
323
- type: 'dropdown',
324
- label: 'GPT Model',
325
- dataKey: 'model',
326
- useInputToggleDataKey: 'useModelInput',
327
- options: openAiModelOptions,
328
- disableIf: (data) => {
329
- return !!data.overrideModel?.trim();
330
- },
331
- helperMessage: (data) => {
332
- if (data.overrideModel?.trim()) {
333
- return `Model overridden to: ${data.overrideModel}`;
334
- }
335
- if (data.model === 'local-model') {
336
- return 'Local model is an indicator for your own convenience, it does not affect the local LLM used.';
337
- }
338
- },
339
- },
340
- {
341
- type: 'group',
342
- label: 'Parameters',
343
- editors: [
344
- {
345
- type: 'number',
346
- label: 'Temperature',
347
- dataKey: 'temperature',
348
- useInputToggleDataKey: 'useTemperatureInput',
349
- min: 0,
350
- max: 2,
351
- step: 0.1,
352
- 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.',
353
- },
354
- {
355
- type: 'number',
356
- label: 'Top P',
357
- dataKey: 'top_p',
358
- useInputToggleDataKey: 'useTopPInput',
359
- min: 0,
360
- max: 1,
361
- step: 0.1,
362
- 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.',
363
- },
364
- {
365
- type: 'toggle',
366
- label: 'Use Top P',
367
- dataKey: 'useTopP',
368
- useInputToggleDataKey: 'useUseTopPInput',
369
- helperMessage: 'Whether to use top p sampling, or temperature sampling.',
370
- },
371
- {
372
- type: 'number',
373
- label: 'Max Tokens',
374
- dataKey: 'maxTokens',
375
- useInputToggleDataKey: 'useMaxTokensInput',
376
- min: 0,
377
- max: Number.MAX_SAFE_INTEGER,
378
- step: 1,
379
- helperMessage: 'The maximum number of tokens to generate in the chat completion.',
380
- },
381
- {
382
- type: 'string',
383
- label: 'Stop',
384
- dataKey: 'stop',
385
- useInputToggleDataKey: 'useStopInput',
386
- helperMessage: 'A sequence where the API will stop generating further tokens.',
387
- },
388
- {
389
- type: 'number',
390
- label: 'Presence Penalty',
391
- dataKey: 'presencePenalty',
392
- useInputToggleDataKey: 'usePresencePenaltyInput',
393
- min: 0,
394
- max: 2,
395
- step: 0.1,
396
- allowEmpty: true,
397
- 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.`,
398
- },
399
- {
400
- type: 'number',
401
- label: 'Frequency Penalty',
402
- dataKey: 'frequencyPenalty',
403
- useInputToggleDataKey: 'useFrequencyPenaltyInput',
404
- min: 0,
405
- max: 2,
406
- step: 0.1,
407
- allowEmpty: true,
408
- 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.`,
409
- },
410
- {
411
- type: 'dropdown',
412
- label: 'Response Format',
413
- dataKey: 'responseFormat',
414
- useInputToggleDataKey: 'useResponseFormatInput',
415
- options: [
416
- { value: '', label: 'Default' },
417
- { value: 'text', label: 'Text' },
418
- { value: 'json', label: 'JSON Object' },
419
- { value: 'json_schema', label: 'JSON Schema' },
420
- ],
421
- defaultValue: '',
422
- helperMessage: 'The format to force the model to reply in.',
423
- },
424
- {
425
- type: 'string',
426
- label: 'Response Schema Name',
427
- dataKey: 'responseSchemaName',
428
- useInputToggleDataKey: 'useResponseSchemaNameInput',
429
- helperMessage: 'The name of the JSON schema that the response will adhere to (Structured Outputs). Defaults to response_schema',
430
- hideIf: (data) => data.responseFormat !== 'json_schema',
431
- },
432
- {
433
- type: 'number',
434
- label: 'Seed',
435
- dataKey: 'seed',
436
- useInputToggleDataKey: 'useSeedInput',
437
- step: 1,
438
- allowEmpty: true,
439
- 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.',
440
- },
441
- ],
442
- },
443
- {
444
- type: 'group',
445
- label: 'GPT Tools',
446
- editors: [
447
- {
448
- type: 'toggle',
449
- label: 'Enable Function Use',
450
- dataKey: 'enableFunctionUse',
451
- },
452
- {
453
- type: 'toggle',
454
- label: 'Enable Parallel Function Calling',
455
- dataKey: 'parallelFunctionCalling',
456
- hideIf: (data) => !data.enableFunctionUse,
457
- },
458
- {
459
- type: 'dropdown',
460
- label: 'Tool Choice',
461
- dataKey: 'toolChoice',
462
- useInputToggleDataKey: 'useToolChoiceInput',
463
- options: [
464
- { value: '', label: 'Default' },
465
- { value: 'none', label: 'None' },
466
- { value: 'auto', label: 'Auto' },
467
- { value: 'function', label: 'Function' },
468
- { value: 'required', label: 'Required' },
469
- ],
470
- defaultValue: '',
471
- 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.',
472
- hideIf: (data) => !data.enableFunctionUse,
473
- },
474
- {
475
- type: 'string',
476
- label: 'Tool Choice Function',
477
- dataKey: 'toolChoiceFunction',
478
- useInputToggleDataKey: 'useToolChoiceFunctionInput',
479
- helperMessage: 'The name of the function to force the model to call.',
480
- hideIf: (data) => data.toolChoice !== 'function' || !data.enableFunctionUse,
481
- },
482
- ],
483
- },
484
- {
485
- type: 'group',
486
- label: 'Advanced',
487
- editors: [
488
- {
489
- type: 'string',
490
- label: 'User',
491
- dataKey: 'user',
492
- useInputToggleDataKey: 'useUserInput',
493
- helperMessage: 'A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.',
494
- },
495
- {
496
- type: 'number',
497
- label: 'Number of Choices',
498
- dataKey: 'numberOfChoices',
499
- useInputToggleDataKey: 'useNumberOfChoicesInput',
500
- min: 1,
501
- max: 10,
502
- step: 1,
503
- defaultValue: 1,
504
- helperMessage: 'If greater than 1, the model will return multiple choices and the response will be an array.',
505
- },
506
- {
507
- type: 'string',
508
- label: 'Endpoint',
509
- dataKey: 'endpoint',
510
- useInputToggleDataKey: 'useEndpointInput',
511
- 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',
512
- },
513
- {
514
- type: 'string',
515
- label: 'Custom Model',
516
- dataKey: 'overrideModel',
517
- helperMessage: 'Overrides the model selected above with a custom string for the model.',
518
- },
519
- {
520
- type: 'number',
521
- label: 'Custom Max Tokens',
522
- dataKey: 'overrideMaxTokens',
523
- allowEmpty: true,
524
- helperMessage: 'Overrides the max number of tokens a model can support. Leave blank for preconfigured token limits.',
525
- },
526
- {
527
- type: 'keyValuePair',
528
- label: 'Headers',
529
- dataKey: 'headers',
530
- useInputToggleDataKey: 'useHeadersInput',
531
- keyPlaceholder: 'Header',
532
- helperMessage: 'Additional headers to send to the API.',
533
- },
534
- {
535
- type: 'toggle',
536
- label: 'Cache In Rivet',
537
- dataKey: 'cache',
538
- helperMessage: 'If on, requests with the same parameters and messages will be cached in Rivet, for immediate responses without an API call.',
539
- },
540
- {
541
- type: 'toggle',
542
- label: 'Use for subgraph partial output',
543
- dataKey: 'useAsGraphPartialOutput',
544
- helperMessage: 'If on, streaming responses from this node will be shown in Subgraph nodes that call this graph.',
545
- },
546
- {
547
- type: 'keyValuePair',
548
- label: 'Additional Parameters',
549
- dataKey: 'additionalParameters',
550
- useInputToggleDataKey: 'useAdditionalParametersInput',
551
- keyPlaceholder: 'Parameter',
552
- valuePlaceholder: 'Value',
553
- 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.',
554
- },
555
- ],
556
- },
557
- ];
45
+ return ChatNodeBase.getEditors();
558
46
  }
559
47
  getBody() {
560
- return dedent `
561
- ${this.data.endpoint ? `${this.data.endpoint}` : ''}
562
- ${this.data.useMaxTokensInput ? 'Max Tokens: (Using Input)' : `${this.data.maxTokens} tokens`}
563
- Model: ${this.data.useModelInput ? '(Using Input)' : this.data.overrideModel || this.data.model}
564
- ${this.data.useTopP ? 'Top P' : 'Temperature'}:
565
- ${this.data.useTopP
566
- ? this.data.useTopPInput
567
- ? '(Using Input)'
568
- : this.data.top_p
569
- : this.data.useTemperatureInput
570
- ? '(Using Input)'
571
- : this.data.temperature}
572
- ${this.data.useStop ? `Stop: ${this.data.useStopInput ? '(Using Input)' : this.data.stop}` : ''}
573
- ${(this.data.frequencyPenalty ?? 0) !== 0
574
- ? `Frequency Penalty: ${this.data.useFrequencyPenaltyInput ? '(Using Input)' : this.data.frequencyPenalty}`
575
- : ''}
576
- ${(this.data.presencePenalty ?? 0) !== 0
577
- ? `Presence Penalty: ${this.data.usePresencePenaltyInput ? '(Using Input)' : this.data.presencePenalty}`
578
- : ''}
579
- `.trim();
48
+ return ChatNodeBase.getBody(this.data);
580
49
  }
581
50
  async process(inputs, context) {
582
- const output = {};
583
- const model = getInputOrData(this.data, inputs, 'model');
584
- const temperature = getInputOrData(this.data, inputs, 'temperature', 'number');
585
- const topP = this.data.useTopPInput
586
- ? coerceTypeOptional(inputs['top_p'], 'number') ?? this.data.top_p
587
- : this.data.top_p;
588
- const useTopP = getInputOrData(this.data, inputs, 'useTopP', 'boolean');
589
- const stop = this.data.useStopInput
590
- ? this.data.useStop
591
- ? coerceTypeOptional(inputs['stop'], 'string') ?? this.data.stop
592
- : undefined
593
- : this.data.stop;
594
- const presencePenalty = getInputOrData(this.data, inputs, 'presencePenalty', 'number');
595
- const frequencyPenalty = getInputOrData(this.data, inputs, 'frequencyPenalty', 'number');
596
- const numberOfChoices = getInputOrData(this.data, inputs, 'numberOfChoices', 'number');
597
- const endpoint = getInputOrData(this.data, inputs, 'endpoint');
598
- const overrideModel = getInputOrData(this.data, inputs, 'overrideModel');
599
- const seed = getInputOrData(this.data, inputs, 'seed', 'number');
600
- const responseFormat = getInputOrData(this.data, inputs, 'responseFormat');
601
- const toolChoiceMode = getInputOrData(this.data, inputs, 'toolChoice', 'string');
602
- const toolChoice = !toolChoiceMode || !this.data.enableFunctionUse
603
- ? undefined
604
- : toolChoiceMode === 'function'
605
- ? {
606
- type: 'function',
607
- function: {
608
- name: getInputOrData(this.data, inputs, 'toolChoiceFunction', 'string'),
609
- },
610
- }
611
- : toolChoiceMode;
612
- let responseSchema;
613
- const responseSchemaInput = inputs['responseSchema'];
614
- if (responseSchemaInput?.type === 'gpt-function') {
615
- responseSchema = responseSchemaInput.value.parameters;
616
- }
617
- else if (responseSchemaInput != null) {
618
- responseSchema = coerceType(responseSchemaInput, 'object');
619
- }
620
- const openaiResponseFormat = !responseFormat?.trim()
621
- ? undefined
622
- : responseFormat === 'json'
623
- ? {
624
- type: 'json_object',
625
- }
626
- : responseFormat === 'json_schema'
627
- ? {
628
- type: 'json_schema',
629
- json_schema: {
630
- name: getInputOrData(this.data, inputs, 'responseSchemaName', 'string') || 'response_schema',
631
- strict: true,
632
- schema: responseSchema ?? {},
633
- },
634
- }
635
- : {
636
- type: 'text',
637
- };
638
- const headersFromData = (this.data.headers ?? []).reduce((acc, header) => {
639
- acc[header.key] = header.value;
640
- return acc;
641
- }, {});
642
- const additionalHeaders = this.data.useHeadersInput
643
- ? coerceTypeOptional(inputs['headers'], 'object') ??
644
- headersFromData
645
- : headersFromData;
646
- const additionalParametersFromData = (this.data.additionalParameters ?? []).reduce((acc, param) => {
647
- acc[param.key] = Number.isNaN(parseFloat(param.value)) ? param.value : parseFloat(param.value);
648
- return acc;
649
- }, {});
650
- const additionalParameters = this.data.useAdditionalParametersInput
651
- ? coerceTypeOptional(inputs['additionalParameters'], 'object') ?? additionalParametersFromData
652
- : additionalParametersFromData;
653
- // If using a model input, that's priority, otherwise override > main
654
- const finalModel = this.data.useModelInput && inputs['model'] != null ? model : overrideModel || model;
655
- const functions = coerceTypeOptional(inputs['functions'], 'gpt-function[]');
656
- const tools = (functions ?? []).map((fn) => ({
657
- function: fn,
658
- type: 'function',
659
- }));
660
- const { messages } = getChatNodeMessages(inputs);
661
- const completionMessages = await Promise.all(messages.map((message) => chatMessageToOpenAIChatCompletionMessage(message)));
662
- let { maxTokens } = this.data;
663
- const openaiModel = {
664
- ...(openaiModels[model] ?? {
665
- maxTokens: this.data.overrideMaxTokens ?? 8192,
666
- cost: {
667
- completion: 0,
668
- prompt: 0,
669
- },
670
- displayName: 'Custom Model',
671
- }),
672
- };
673
- if (this.data.overrideMaxTokens) {
674
- openaiModel.maxTokens = this.data.overrideMaxTokens;
675
- }
676
- const isMultiResponse = this.data.useNumberOfChoicesInput || (this.data.numberOfChoices ?? 1) > 1;
677
- // Resolve to final endpoint if configured in ProcessContext
678
- const configuredEndpoint = endpoint || context.settings.openAiEndpoint || DEFAULT_CHAT_ENDPOINT;
679
- const resolvedEndpointAndHeaders = context.getChatNodeEndpoint
680
- ? await context.getChatNodeEndpoint(configuredEndpoint, finalModel)
681
- : {
682
- endpoint: configuredEndpoint,
683
- headers: {},
684
- };
685
- const allAdditionalHeaders = cleanHeaders({
686
- ...context.settings.chatNodeHeaders,
687
- ...additionalHeaders,
688
- ...resolvedEndpointAndHeaders.headers,
689
- });
690
- const tokenizerInfo = {
691
- node: this.chartNode,
692
- model: finalModel,
693
- endpoint: resolvedEndpointAndHeaders.endpoint,
694
- };
695
- const tokenCount = await context.tokenizer.getTokenCountForMessages(messages, functions, tokenizerInfo);
696
- if (tokenCount >= openaiModel.maxTokens) {
697
- throw new Error(`The model ${model} can only handle ${openaiModel.maxTokens} tokens, but ${tokenCount} were provided in the prompts alone.`);
698
- }
699
- if (tokenCount + maxTokens > openaiModel.maxTokens) {
700
- 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 - tokenCount}.`;
701
- addWarning(output, message);
702
- maxTokens = Math.floor((openaiModel.maxTokens - tokenCount) * 0.95); // reduce max tokens by 5% to be safe, calculation is a little wrong.
703
- }
704
- try {
705
- return await retry(async () => {
706
- const options = {
707
- messages: completionMessages,
708
- model: finalModel,
709
- temperature: useTopP ? undefined : temperature,
710
- top_p: useTopP ? topP : undefined,
711
- max_tokens: maxTokens,
712
- n: numberOfChoices,
713
- frequency_penalty: frequencyPenalty,
714
- presence_penalty: presencePenalty,
715
- stop: stop || undefined,
716
- tools: tools.length > 0 ? tools : undefined,
717
- endpoint: resolvedEndpointAndHeaders.endpoint,
718
- seed,
719
- response_format: openaiResponseFormat,
720
- tool_choice: toolChoice,
721
- ...additionalParameters,
722
- };
723
- const cacheKey = JSON.stringify(options);
724
- if (this.data.cache) {
725
- const cached = cache.get(cacheKey);
726
- if (cached) {
727
- return cached;
728
- }
729
- }
730
- const startTime = Date.now();
731
- let usagePromptTokens = -1;
732
- let usageCompletionTokens = -1;
733
- const chunks = streamChatCompletions({
734
- auth: {
735
- apiKey: context.settings.openAiKey ?? '',
736
- organization: context.settings.openAiOrganization,
737
- },
738
- headers: allAdditionalHeaders,
739
- signal: context.signal,
740
- timeout: context.settings.chatNodeTimeout,
741
- ...options,
742
- });
743
- const responseChoicesParts = [];
744
- // First array is the function calls per choice, inner array is the functions calls inside the choice
745
- const functionCalls = [];
746
- for await (const chunk of chunks) {
747
- if (!chunk.choices) {
748
- // Could be error for some reason 🤷‍♂️ but ignoring has worked for me so far.
749
- continue;
750
- }
751
- if (chunk.choices.length == 0 && chunk.usage) { //capture the usage info
752
- usagePromptTokens = chunk.usage.prompt_tokens;
753
- usageCompletionTokens = chunk.usage.completion_tokens;
754
- }
755
- for (const { delta, index } of chunk.choices) {
756
- if (delta.content != null) {
757
- responseChoicesParts[index] ??= [];
758
- responseChoicesParts[index].push(delta.content);
759
- }
760
- if (delta.tool_calls) {
761
- // Are we sure that tool_calls will always be full and not a bunch of deltas?
762
- functionCalls[index] ??= [];
763
- for (const toolCall of delta.tool_calls) {
764
- functionCalls[index][toolCall.index] ??= {
765
- type: 'function',
766
- arguments: '',
767
- lastParsedArguments: undefined,
768
- name: '',
769
- id: '',
770
- };
771
- if (toolCall.id) {
772
- functionCalls[index][toolCall.index].id = toolCall.id;
773
- }
774
- if (toolCall.function.name) {
775
- functionCalls[index][toolCall.index].name += toolCall.function.name;
776
- }
777
- if (toolCall.function.arguments) {
778
- functionCalls[index][toolCall.index].arguments += toolCall.function.arguments;
779
- try {
780
- functionCalls[index][toolCall.index].lastParsedArguments = JSON.parse(functionCalls[index][toolCall.index].arguments);
781
- }
782
- catch (error) {
783
- // Ignore
784
- }
785
- }
786
- }
787
- }
788
- }
789
- if (isMultiResponse) {
790
- output['response'] = {
791
- type: 'string[]',
792
- value: responseChoicesParts.map((parts) => parts.join('')),
793
- };
794
- }
795
- else {
796
- output['response'] = {
797
- type: 'string',
798
- value: responseChoicesParts[0]?.join('') ?? '',
799
- };
800
- }
801
- if (functionCalls.length > 0) {
802
- if (isMultiResponse) {
803
- output['function-call'] = {
804
- type: 'object[]',
805
- value: functionCalls.flat().map((functionCall) => ({
806
- name: functionCall.name,
807
- arguments: functionCall.lastParsedArguments,
808
- id: functionCall.id,
809
- })),
810
- };
811
- }
812
- else {
813
- output['function-call'] = {
814
- type: 'object[]',
815
- value: functionCalls[0].map((functionCall) => ({
816
- name: functionCall.name,
817
- arguments: functionCall.lastParsedArguments,
818
- id: functionCall.id,
819
- })),
820
- };
821
- }
822
- }
823
- context.onPartialOutputs?.(output);
824
- }
825
- if (!isMultiResponse) {
826
- output['all-messages'] = {
827
- type: 'chat-message[]',
828
- value: [
829
- ...messages,
830
- {
831
- type: 'assistant',
832
- message: responseChoicesParts[0]?.join('') ?? '',
833
- function_call: functionCalls[0]
834
- ? {
835
- name: functionCalls[0][0].name,
836
- arguments: functionCalls[0][0].arguments, // Needs the stringified one here in chat list
837
- id: functionCalls[0][0].id,
838
- }
839
- : undefined,
840
- function_calls: functionCalls[0]
841
- ? functionCalls[0].map((fc) => ({
842
- name: fc.name,
843
- arguments: fc.arguments,
844
- id: fc.id,
845
- }))
846
- : undefined,
847
- },
848
- ],
849
- };
850
- }
851
- const endTime = Date.now();
852
- if (responseChoicesParts.length === 0 && functionCalls.length === 0) {
853
- throw new Error('No response from OpenAI');
854
- }
855
- output['in-messages'] = { type: 'chat-message[]', value: messages };
856
- let finalTokenCount = tokenCount * (numberOfChoices ?? 1);
857
- let responseTokenCount = 0;
858
- for (const choiceParts of responseChoicesParts) {
859
- responseTokenCount += await context.tokenizer.getTokenCountForString(choiceParts.join(''), tokenizerInfo);
860
- }
861
- if (usagePromptTokens != -1 && usageCompletionTokens != -1) {
862
- if (finalTokenCount != usagePromptTokens) {
863
- console.log(`calculated token count:${finalTokenCount}, usage:${usagePromptTokens}`);
864
- finalTokenCount = usagePromptTokens;
865
- }
866
- if (responseTokenCount != usageCompletionTokens) {
867
- console.log(`calculated response token count:${responseTokenCount}, usage:${usageCompletionTokens}`);
868
- responseTokenCount = usageCompletionTokens;
869
- }
870
- }
871
- output['requestTokens'] = { type: 'number', value: finalTokenCount };
872
- output['responseTokens'] = { type: 'number', value: responseTokenCount };
873
- const promptCostPerThousand = model in openaiModels ? openaiModels[model].cost.prompt : 0;
874
- const completionCostPerThousand = model in openaiModels ? openaiModels[model].cost.completion : 0;
875
- const promptCost = getCostForTokens(tokenCount, 'prompt', promptCostPerThousand);
876
- const completionCost = getCostForTokens(responseTokenCount, 'completion', completionCostPerThousand);
877
- const cost = promptCost + completionCost;
878
- output['cost'] = { type: 'number', value: cost };
879
- output['__hidden_token_count'] = { type: 'number', value: tokenCount + responseTokenCount };
880
- const duration = endTime - startTime;
881
- output['duration'] = { type: 'number', value: duration };
882
- Object.freeze(output);
883
- cache.set(cacheKey, output);
884
- return output;
885
- }, {
886
- forever: true,
887
- retries: 10000,
888
- maxRetryTime: 1000 * 60 * 5,
889
- factor: 2.5,
890
- minTimeout: 500,
891
- maxTimeout: 5000,
892
- randomize: true,
893
- signal: context.signal,
894
- onFailedAttempt(err) {
895
- if (err.toString().includes('fetch failed') && err.cause) {
896
- const cause = getError(err.cause) instanceof AggregateError
897
- ? err.cause.errors[0]
898
- : getError(err.cause);
899
- err = cause;
900
- }
901
- context.trace(`ChatNode failed, retrying: ${err.toString()}`);
902
- if (context.signal.aborted) {
903
- throw new Error('Aborted');
904
- }
905
- const { retriesLeft } = err;
906
- if (!(err instanceof OpenAIError)) {
907
- if ('code' in err) {
908
- throw err;
909
- }
910
- return; // Just retry?
911
- }
912
- if (err.status === 429) {
913
- if (retriesLeft) {
914
- context.onPartialOutputs?.({
915
- ['response']: {
916
- type: 'string',
917
- value: 'OpenAI API rate limit exceeded, retrying...',
918
- },
919
- });
920
- return;
921
- }
922
- }
923
- if (err.status === 408) {
924
- if (retriesLeft) {
925
- context.onPartialOutputs?.({
926
- ['response']: {
927
- type: 'string',
928
- value: 'OpenAI API timed out, retrying...',
929
- },
930
- });
931
- return;
932
- }
933
- }
934
- // We did something wrong (besides rate limit)
935
- if (err.status >= 400 && err.status < 500) {
936
- throw new Error(err.message);
937
- }
938
- },
939
- });
940
- }
941
- catch (error) {
942
- context.trace(getError(error).stack ?? 'Missing stack');
943
- throw new Error(`Error processing ChatNode: ${error.message}`);
944
- }
51
+ return ChatNodeBase.process(this.data, this.chartNode, inputs, context);
945
52
  }
946
53
  }
947
54
  export const chatNode = nodeDefinition(ChatNodeImpl, 'Chat');
948
- export function getChatNodeMessages(inputs) {
949
- const prompt = inputs['prompt'];
950
- let messages = match(prompt)
951
- .with({ type: 'chat-message' }, (p) => [p.value])
952
- .with({ type: 'chat-message[]' }, (p) => p.value)
953
- .with({ type: 'string' }, (p) => [{ type: 'user', message: p.value }])
954
- .with({ type: 'string[]' }, (p) => p.value.map((v) => ({ type: 'user', message: v })))
955
- .otherwise((p) => {
956
- if (!p) {
957
- return [];
958
- }
959
- if (isArrayDataValue(p)) {
960
- const stringValues = p.value.map((v) => coerceType({
961
- type: getScalarTypeOf(p.type),
962
- value: v,
963
- }, 'string'));
964
- return stringValues.filter((v) => v != null).map((v) => ({ type: 'user', message: v }));
965
- }
966
- const coercedMessage = coerceTypeOptional(p, 'chat-message');
967
- if (coercedMessage != null) {
968
- return [coercedMessage];
969
- }
970
- const coercedString = coerceTypeOptional(p, 'string');
971
- return coercedString != null ? [{ type: 'user', message: coerceType(p, 'string') }] : [];
972
- });
973
- const systemPrompt = inputs['systemPrompt'];
974
- if (systemPrompt) {
975
- messages = [{ type: 'system', message: coerceType(systemPrompt, 'string') }, ...messages];
976
- }
977
- return { messages, systemPrompt };
978
- }
979
- export function getCostForTokens(tokenCount, type, costPerThousand) {
980
- return (tokenCount / 1000) * costPerThousand;
981
- }