node-red-contrib-ollama 0.3.0 → 0.4.0

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.
@@ -11,6 +11,7 @@
11
11
  messagesType: { value: 'msg', required: false },
12
12
  format: { value: '', type: 'ollama-config-format', required: false },
13
13
  stream: { value: false },
14
+ think: { value: false },
14
15
  keepAlive: { value: '5m', required: false },
15
16
  keepAliveType: { value: 'str', required: false },
16
17
  tools: { value: '', type: 'ollama-config-tools', required: false },
@@ -89,6 +90,11 @@
89
90
  <input type="checkbox" id="node-input-stream" />
90
91
  </div>
91
92
 
93
+ <div class="form-row">
94
+ <label for="node-input-think"><i class="fa fa-cog"></i> Think</label>
95
+ <input type="checkbox" id="node-input-think" />
96
+ </div>
97
+
92
98
  <div class="form-row">
93
99
  <label for="node-input-keepAlive"><i class="fa fa-cog"></i> Keep Alive</label>
94
100
  <input type="text" id="node-input-keepAlive" />
@@ -166,6 +172,20 @@
166
172
  </dt>
167
173
  <dd>The format of the response (only <code>json</code> for now).</dd>
168
174
 
175
+ <dt class="optional">
176
+ payload.stream <span class="property-type">boolean</span>
177
+ </dt>
178
+ <dd>
179
+ When true an <code>AsyncGenerator</code> is returned.
180
+ </dd>
181
+
182
+ <dt class="optional">
183
+ payload.think <span class="property-type">boolean</span>
184
+ </dt>
185
+ <dd>
186
+ When true, the model will think about the response before responding. Requires thinking support from the model.
187
+ </dd>
188
+
169
189
  <dt class="optional">
170
190
  payload.keep_alive <span class="property-type">array</span>
171
191
  </dt>
@@ -1,7 +1,7 @@
1
1
  module.exports = function( RED ) {
2
2
  const { Ollama } = require( 'ollama' )
3
3
 
4
- function OllamaChatNode( config ) {
4
+ function OllamaChatNode( config ) {
5
5
  RED.nodes.createNode( this, config )
6
6
 
7
7
  this.modelType = config.modelType || 'str'
@@ -51,6 +51,8 @@ module.exports = function( RED ) {
51
51
 
52
52
  const stream = ( msg?.payload?.stream !== undefined ) ? msg?.payload?.stream : node.stream
53
53
 
54
+ const think = ( msg?.payload?.think !== undefined ) ? msg?.payload?.think : config.think || false
55
+
54
56
  let keep_alive = null
55
57
  if( msg?.payload?.keep_alive ) {
56
58
  keep_alive = msg?.payload?.keep_alive
@@ -73,6 +75,7 @@ module.exports = function( RED ) {
73
75
  messages,
74
76
  format,
75
77
  stream,
78
+ think,
76
79
  keep_alive,
77
80
  tools,
78
81
  options
@@ -5,12 +5,16 @@
5
5
  defaults: {
6
6
  name: { value: 'Ollama Create' },
7
7
  server: { value: '', type: 'ollama-config-server', required: false },
8
- model: { value: '', required: false },
9
- modelType: { value: 'str', required: false },
10
- modelfile: { value: '', required: false },
11
- stream: { value: false },
12
- path: { value: '', required: false },
13
- pathType: { value: 'str', required: false },
8
+ model: { value: '', type: 'typedInput', required: true },
9
+ from: { value: '', type: 'typedInput', required: true },
10
+ stream: { value: false, type: 'boolean', required: false },
11
+ quantize: { value: '', type: 'typedInput', required: true },
12
+ template: { value: '', type: 'typedInput', required: false },
13
+ license: { value: '', type: 'typedInput', required: false },
14
+ system: { value: '', type: 'typedInput', required: false },
15
+ parameters: { value: '', type: 'typedInput', required: false },
16
+ messages: { value: '', type: 'typedInput', required: false },
17
+ adapters: { value: '', type: 'typedInput', required: false }
14
18
  },
15
19
  inputs: 1,
16
20
  outputs: 1,
@@ -29,21 +33,52 @@
29
33
  typeField: '#node-input-modelType'
30
34
  } )
31
35
 
32
- this.editor = RED.editor.createEditor( {
33
- id: 'node-input-modelfile-editor',
34
- mode: 'yaml',
35
- value: this.modelfile,
36
- mode: 'ace/mode/text'
36
+ $( '#node-input-from' ).typedInput( {
37
+ type: 'str',
38
+ types: [ 'str', 'msg', 'flow', 'global' ],
39
+ typeField: '#node-input-fromType'
37
40
  } )
38
41
 
39
- if( !this.pathType ) {
40
- this.pathType = 'str'
41
- }
42
+ $( '#node-input-quantize' ).typedInput( {
43
+ type: 'str',
44
+ types: [ 'str', 'msg', 'flow', 'global' ],
45
+ typeField: '#node-input-quantizeType'
46
+ } )
42
47
 
43
- $( '#node-input-path' ).typedInput( {
48
+ $( '#node-input-template' ).typedInput( {
44
49
  type: 'str',
45
50
  types: [ 'str', 'msg', 'flow', 'global' ],
46
- typeField: '#node-input-pathType'
51
+ typeField: '#node-input-templateType'
52
+ } )
53
+
54
+ $( '#node-input-license' ).typedInput( {
55
+ type: 'str',
56
+ types: [ 'str', 'msg', 'flow', 'global' ],
57
+ typeField: '#node-input-licenseType'
58
+ } )
59
+
60
+ $( '#node-input-system' ).typedInput( {
61
+ type: 'str',
62
+ types: [ 'str', 'msg', 'flow', 'global' ],
63
+ typeField: '#node-input-systemType'
64
+ } )
65
+
66
+ $( '#node-input-parameters' ).typedInput( {
67
+ type: 'str',
68
+ types: [ 'str', 'msg', 'flow', 'global' ],
69
+ typeField: '#node-input-parametersType'
70
+ } )
71
+
72
+ $( '#node-input-messages' ).typedInput( {
73
+ type: 'str',
74
+ types: [ 'str', 'msg', 'flow', 'global' ],
75
+ typeField: '#node-input-messagesType'
76
+ } )
77
+
78
+ $( '#node-input-adapters' ).typedInput( {
79
+ type: 'str',
80
+ types: [ 'str', 'msg', 'flow', 'global' ],
81
+ typeField: '#node-input-adaptersType'
47
82
  } )
48
83
  },
49
84
  oneditsave: function() {
@@ -76,18 +111,57 @@
76
111
  </div>
77
112
 
78
113
  <div class="form-row">
79
- <label for="node-input-stream"><i class="fa fa-cog"></i> Stream</label>
114
+ <label for="node-input-from"><i class="fa fa-file"></i> From</label>
115
+ <input type="text" id="node-input-from" />
116
+ <input type="hidden" id="node-input-fromType" />
117
+ </div>
118
+
119
+ <div class="form-row">
120
+ <label for="node-input-stream"><i class="fa fa-stream"></i> Stream</label>
80
121
  <input type="checkbox" id="node-input-stream" />
81
122
  </div>
82
123
 
83
124
  <div class="form-row">
84
- <label for="node-input-path"><i class="fa fa-comment"></i> Path</label>
85
- <input type="text" id="node-input-path" />
86
- <input type="hidden" id="node-input-pathType" />
125
+ <label for="node-input-quantize"><i class="fa fa-compress"></i> Quantize</label>
126
+ <input type="text" id="node-input-quantize" />
127
+ <input type="hidden" id="node-input-quantizeType" />
128
+ </div>
129
+
130
+ <div class="form-row">
131
+ <label for="node-input-template"><i class="fa fa-file-code"></i> Template</label>
132
+ <input type="text" id="node-input-template" />
133
+ <input type="hidden" id="node-input-templateType" />
134
+ </div>
135
+
136
+ <div class="form-row">
137
+ <label for="node-input-license"><i class="fa fa-certificate"></i> License</label>
138
+ <input type="text" id="node-input-license" />
139
+ <input type="hidden" id="node-input-licenseType" />
140
+ </div>
141
+
142
+ <div class="form-row">
143
+ <label for="node-input-system"><i class="fa fa-cog"></i> System</label>
144
+ <input type="text" id="node-input-system" />
145
+ <input type="hidden" id="node-input-systemType" />
87
146
  </div>
88
147
 
89
- <label>Modelfile</label>
90
- <div style="height: 250px; min-height:150px;" class="node-text-editor" id="node-input-modelfile-editor" />
148
+ <div class="form-row">
149
+ <label for="node-input-parameters"><i class="fa fa-cogs"></i> Parameters</label>
150
+ <input type="text" id="node-input-parameters" />
151
+ <input type="hidden" id="node-input-parametersType" />
152
+ </div>
153
+
154
+ <div class="form-row">
155
+ <label for="node-input-messages"><i class="fa fa-comments"></i> Messages</label>
156
+ <input type="text" id="node-input-messages" />
157
+ <input type="hidden" id="node-input-messagesType" />
158
+ </div>
159
+
160
+ <div class="form-row">
161
+ <label for="node-input-adapters"><i class="fa fa-plug"></i> Adapters</label>
162
+ <input type="text" id="node-input-adapters" />
163
+ <input type="hidden" id="node-input-adaptersType" />
164
+ </div>
91
165
  </script>
92
166
 
93
167
  <script type="text/html" data-help-name="ollama-create">
@@ -116,19 +190,49 @@
116
190
  <dd>The name of the model to create.</dd>
117
191
 
118
192
  <dt class="optional">
119
- payload.path <span class="property-type">string</span>
193
+ payload.from <span class="property-type">string</span>
120
194
  </dt>
121
- <dd>The path to the Modelfile to use for creating the model.</dd>
195
+ <dd>The base model to derive from.</dd>
122
196
 
123
197
  <dt class="optional">
124
- payload.modelfile <span class="property-type">string</span>
198
+ payload.stream <span class="property-type">boolean</span>
125
199
  </dt>
126
- <dd>The content of the Modelfile to create.</dd>
200
+ <dd>When true an <code>AsyncGenerator</code> is returned.</dd>
127
201
 
128
202
  <dt class="optional">
129
- payload.stream <span class="property-type">boolean</span>
203
+ payload.quantize <span class="property-type">string</span>
204
+ </dt>
205
+ <dd>Quanization precision level (<code>q8_0</code>, <code>q4_K_M</code>, etc.).</dd>
206
+
207
+ <dt class="optional">
208
+ payload.template <span class="property-type">string</span>
209
+ </dt>
210
+ <dd>The prompt template to use with the model.</dd>
211
+
212
+ <dt class="optional">
213
+ payload.license <span class="property-type">string</span>
214
+ </dt>
215
+ <dd>The license(s) associated with the model.</dd>
216
+
217
+ <dt class="optional">
218
+ payload.system <span class="property-type">string</span>
219
+ </dt>
220
+ <dd>The system prompt for the model.</dd>
221
+
222
+ <dt class="optional">
223
+ payload.parameters <span class="property-type">object</span>
224
+ </dt>
225
+ <dd>Additional model parameters as key-value pairs.</dd>
226
+
227
+ <dt class="optional">
228
+ payload.messages <span class="property-type">array</span>
229
+ </dt>
230
+ <dd>Initial chat messages for the model.</dd>
231
+
232
+ <dt class="optional">
233
+ payload.adapters <span class="property-type">object</span>
130
234
  </dt>
131
- <dd>When true an AsyncGenerator is returned.</dd>
235
+ <dd>A key-value map of LoRA adapter configurations.</dd>
132
236
  </dl>
133
237
  </dd>
134
238
  </dl>
@@ -5,7 +5,15 @@ module.exports = function( RED ) {
5
5
  RED.nodes.createNode( this, config )
6
6
 
7
7
  this.modelType = config.modelType || 'str'
8
+ this.fromType = config.from || 'str'
8
9
  this.stream = config.stream || false
10
+ this.quantize = config.quantize || 'str'
11
+ this.template = config.template || null
12
+ this.license = config.license || null
13
+ this.system = config.system || null
14
+ this.parameters = config.parameters || 'str'
15
+ this.messages = config.messages || 'str'
16
+ this.adapters = config.adapters || 'str'
9
17
  this.pathType = config.pathType || 'str'
10
18
 
11
19
  const node = this
@@ -30,30 +38,139 @@ module.exports = function( RED ) {
30
38
  }
31
39
  }
32
40
 
33
- const modelfile = msg?.payload?.modelfile || config.modelfile
41
+ let from = null
42
+ if( msg?.payload?.from ) {
43
+ from = msg?.payload?.from
44
+ } else if( !!config.from ) {
45
+ if( node.fromType === 'str' ) {
46
+ from = config.from
47
+ } else if( node.fromType === 'msg' ) {
48
+ from = msg[ config.from ]
49
+ } else if( node.fromType === 'flow' ) {
50
+ from = node.context().flow.get( config.from )
51
+ } else if( node.fromType === 'global' ) {
52
+ from = node.context().global.get( config.from )
53
+ }
54
+ }
34
55
 
35
56
  const stream = ( msg?.payload?.stream !== undefined ) ? msg?.payload?.stream : node.stream
36
57
 
37
- let path = null
38
- if( msg?.payload?.path ) {
39
- path = msg?.payload?.path
40
- } else if( !!config.path ) {
41
- if( node.pathType === 'str' ) {
42
- path = config.path
43
- } else if( node.pathType === 'msg' ) {
44
- path = msg[ config.path ]
45
- } else if( node.pathType === 'flow' ) {
46
- path = node.context().flow.get( config.path )
47
- } else if( node.pathType === 'global' ) {
48
- path = node.context().global.get( config.path )
58
+ let quantize = null
59
+ if( msg?.payload?.quantize ) {
60
+ quantize = msg?.payload?.quantize
61
+ } else if( !!config.quantize ) {
62
+ if( node.quantize === 'str' ) {
63
+ quantize = config.quantize
64
+ } else if( node.quantize === 'msg' ) {
65
+ quantize = msg[ config.quantize ]
66
+ } else if( node.quantize === 'flow' ) {
67
+ quantize = node.context().flow.get( config.quantize )
68
+ } else if( node.quantize === 'global' ) {
69
+ quantize = node.context().global.get( config.quantize )
70
+ }
71
+ }
72
+
73
+ let template = null
74
+ if( msg?.payload?.template ) {
75
+ template = msg?.payload?.template
76
+ } else if( !!config.template ) {
77
+ if( node.template === 'str' ) {
78
+ template = config.template
79
+ } else if( node.template === 'msg' ) {
80
+ template = msg[ config.template ]
81
+ } else if( node.template === 'flow' ) {
82
+ template = node.context().flow.get( config.template )
83
+ } else if( node.template === 'global' ) {
84
+ template = node.context().global.get( config.template )
85
+ }
86
+ }
87
+
88
+ let license = null
89
+ if( msg?.payload?.license ) {
90
+ license = msg?.payload?.license
91
+ } else if( !!config.license ) {
92
+ if( node.license === 'str' ) {
93
+ license = config.license
94
+ } else if( node.license === 'msg' ) {
95
+ license = msg[ config.license ]
96
+ } else if( node.license === 'flow' ) {
97
+ license = node.context().flow.get( config.license )
98
+ } else if( node.license === 'global' ) {
99
+ license = node.context().global.get( config.license )
100
+ }
101
+ }
102
+
103
+ let system = null
104
+ if( msg?.payload?.system ) {
105
+ system = msg?.payload?.system
106
+ } else if( !!config.system ) {
107
+ if( node.system === 'str' ) {
108
+ system = config.system
109
+ } else if( node.system === 'msg' ) {
110
+ system = msg[ config.system ]
111
+ } else if( node.system === 'flow' ) {
112
+ system = node.context().flow.get( config.system )
113
+ } else if( node.system === 'global' ) {
114
+ system = node.context().global.get( config.system )
115
+ }
116
+ }
117
+
118
+ let parameters = null
119
+ if( msg?.payload?.parameters ) {
120
+ parameters = msg?.payload?.parameters
121
+ } else if( !!config.parameters ) {
122
+ if( node.parameters === 'str' ) {
123
+ parameters = config.parameters
124
+ } else if( node.parameters === 'msg' ) {
125
+ parameters = msg[ config.parameters ]
126
+ } else if( node.parameters === 'flow' ) {
127
+ parameters = node.context().flow.get( config.parameters )
128
+ } else if( node.parameters === 'global' ) {
129
+ parameters = node.context().global.get( config.parameters )
130
+ }
131
+ }
132
+
133
+ let messages = null
134
+ if( msg?.payload?.messages ) {
135
+ messages = msg?.payload?.messages
136
+ } else if( !!config.messages ) {
137
+ if( node.messages === 'str' ) {
138
+ messages = config.messages
139
+ } else if( node.messages === 'msg' ) {
140
+ messages = msg[ config.messages ]
141
+ } else if( node.messages === 'flow' ) {
142
+ messages = node.context().flow.get( config.messages )
143
+ } else if( node.messages === 'global' ) {
144
+ messages = node.context().global.get( config.messages )
145
+ }
146
+ }
147
+
148
+ let adapters = null
149
+ if( msg?.payload?.adapters ) {
150
+ adapters = msg?.payload?.adapters
151
+ } else if( !!config.adapters ) {
152
+ if( node.adapters === 'str' ) {
153
+ adapters = config.adapters
154
+ } else if( node.adapters === 'msg' ) {
155
+ adapters = msg[ config.adapters ]
156
+ } else if( node.adapters === 'flow' ) {
157
+ adapters = node.context().flow.get( config.adapters )
158
+ } else if( node.adapters === 'global' ) {
159
+ adapters = node.context().global.get( config.adapters )
49
160
  }
50
161
  }
51
162
 
52
163
  const response = await ollama.create( {
53
164
  model,
54
- modelfile,
165
+ from,
55
166
  stream,
56
- path
167
+ quantize,
168
+ template,
169
+ license,
170
+ system,
171
+ parameters,
172
+ messages,
173
+ adapters
57
174
  } )
58
175
  .catch( error => {
59
176
  node.error( error )
@@ -20,6 +20,7 @@
20
20
  images: { value: '', required: false },
21
21
  imagesType: { value: 'json', required: false },
22
22
  stream: { value: false },
23
+ think: { value: false },
23
24
  keepAlive: { value: '5m', required: false },
24
25
  keepAliveType: { value: 'str', required: false },
25
26
  options: { value: '', type: 'ollama-config-options', required: false },
@@ -166,6 +167,11 @@
166
167
  <input type="checkbox" id="node-input-stream" />
167
168
  </div>
168
169
 
170
+ <div class="form-row">
171
+ <label for="node-input-think"><i class="fa fa-cog"></i> Think</label>
172
+ <input type="checkbox" id="node-input-think" />
173
+ </div>
174
+
169
175
  <div class="form-row">
170
176
  <label for="node-input-keepAlive"><i class="fa fa-cog"></i> Keep Alive</label>
171
177
  <input type="text" id="node-input-keepAlive" />
@@ -243,6 +249,13 @@
243
249
  </dt>
244
250
  <dd>If <code>false</code> the response will be returned as a single response object, rather than a stream of objects.</dd>
245
251
 
252
+ <dt class="optional">
253
+ payload.think <span class="property-type">boolean</span>
254
+ </dt>
255
+ <dd>
256
+ When <code>true</code>, the model will think about the response before responding. Requires thinking support from the model.
257
+ </dd>
258
+
246
259
  <dt class="optional">
247
260
  payload.raw <span class="property-type">boolean</span>
248
261
  </dt>
@@ -12,6 +12,7 @@ module.exports = function( RED ) {
12
12
  this.raw = config.raw || false
13
13
  this.imagesType = config.imagesType || 'json'
14
14
  this.stream = config.stream || false
15
+ this.think = config.think || false
15
16
  this.keepAliveType = config.keepAliveType || 'str'
16
17
 
17
18
  const node = this
@@ -118,6 +119,8 @@ module.exports = function( RED ) {
118
119
 
119
120
  const stream = ( msg?.payload?.stream !== undefined ) ? msg?.payload?.stream : node.stream
120
121
 
122
+ const think = ( msg?.payload?.think !== undefined ) ? msg?.payload?.think : node.think || false
123
+
121
124
  let keep_alive = null
122
125
  if( msg?.payload?.keep_alive ) {
123
126
  keep_alive = msg?.payload?.keep_alive
@@ -142,6 +145,7 @@ module.exports = function( RED ) {
142
145
  images,
143
146
  format,
144
147
  stream,
148
+ think,
145
149
  keep_alive,
146
150
  options
147
151
  } )
@@ -1,7 +1,7 @@
1
1
  module.exports = function( RED ) {
2
2
  const { Ollama } = require( 'ollama' )
3
3
 
4
- function OllamaPulltNode( config ) {
4
+ function OllamaPullNode( config ) {
5
5
  RED.nodes.createNode( this, config )
6
6
 
7
7
  this.modelType = config.modelType || 'str'
@@ -48,5 +48,5 @@ module.exports = function( RED ) {
48
48
  } )
49
49
  }
50
50
 
51
- RED.nodes.registerType( 'ollama-pull', OllamaPulltNode )
51
+ RED.nodes.registerType( 'ollama-pull', OllamaPullNode )
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ollama",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Add AI functionality to your flows! This module includes a set of nodes that enable easy communication with Ollama, enriching your projects with intelligent solutions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -39,7 +39,7 @@
39
39
  "node": ">=18.0.0"
40
40
  },
41
41
  "dependencies": {
42
- "ollama": "^0.5.11"
42
+ "ollama": "^0.5.16"
43
43
  },
44
44
  "bugs": {
45
45
  "url": "https://github.com/jakubburkiewicz/node-red-contrib-ollama/issues"