node-red-contrib-ollama 0.2.6 → 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.
@@ -5,7 +5,16 @@
5
5
  defaults: {
6
6
  name: { value: 'Ollama Create' },
7
7
  server: { value: '', type: 'ollama-config-server', required: false },
8
- model: { value: '', type: 'ollama-config-model', 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 }
9
18
  },
10
19
  inputs: 1,
11
20
  outputs: 1,
@@ -13,6 +22,74 @@
13
22
  label: function() {
14
23
  return this.name || 'ollama-create';
15
24
  },
25
+ oneditprepare: function() {
26
+ if( !this.modelType ) {
27
+ this.modelType = 'str'
28
+ }
29
+
30
+ $( '#node-input-model' ).typedInput( {
31
+ type: 'str',
32
+ types: [ 'str', 'msg', 'flow', 'global' ],
33
+ typeField: '#node-input-modelType'
34
+ } )
35
+
36
+ $( '#node-input-from' ).typedInput( {
37
+ type: 'str',
38
+ types: [ 'str', 'msg', 'flow', 'global' ],
39
+ typeField: '#node-input-fromType'
40
+ } )
41
+
42
+ $( '#node-input-quantize' ).typedInput( {
43
+ type: 'str',
44
+ types: [ 'str', 'msg', 'flow', 'global' ],
45
+ typeField: '#node-input-quantizeType'
46
+ } )
47
+
48
+ $( '#node-input-template' ).typedInput( {
49
+ type: 'str',
50
+ types: [ 'str', 'msg', 'flow', 'global' ],
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'
82
+ } )
83
+ },
84
+ oneditsave: function() {
85
+ this.modelfile = this.editor.getValue()
86
+ this.editor.destroy()
87
+ delete this.editor
88
+ },
89
+ oneditcancel: function() {
90
+ this.editor.destroy()
91
+ delete this.editor
92
+ }
16
93
  } )
17
94
  </script>
18
95
 
@@ -28,8 +105,62 @@
28
105
  </div>
29
106
 
30
107
  <div class="form-row">
31
- <label for="node-input-model"><i class="fa fa-bookmark"></i> Model</label>
108
+ <label for="node-input-model"><i class="fa fa-comment"></i> Model</label>
32
109
  <input type="text" id="node-input-model" />
110
+ <input type="hidden" id="node-input-modelType" />
111
+ </div>
112
+
113
+ <div class="form-row">
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>
121
+ <input type="checkbox" id="node-input-stream" />
122
+ </div>
123
+
124
+ <div class="form-row">
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" />
146
+ </div>
147
+
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" />
33
164
  </div>
34
165
  </script>
35
166
 
@@ -44,39 +175,64 @@
44
175
  </dt>
45
176
 
46
177
  <dd>
47
- The payload object.
178
+ The payload object is an alternative way to configure the node.<br />
179
+ Any properties in the payload object will override the node configuration.
48
180
 
49
181
  <dl class="message-properties">
50
182
  <dt class="optional">
51
183
  payload.host <span class="property-type">string</span>
52
184
  </dt>
53
- <dd>
54
- The host of the Ollama instance.<br />
55
- <em>Alternative to the <code>Server</code> field.</em>
56
- </dd>
185
+ <dd>The host of the Ollama instance.</dd>
57
186
 
58
187
  <dt class="optional">
59
188
  payload.model <span class="property-type">string</span>
60
189
  </dt>
61
- <dd>
62
- The name of the model to create.<br />
63
- <em>Alternative to the <code>Model</code> field.</em>
64
- </dd>
190
+ <dd>The name of the model to create.</dd>
65
191
 
66
192
  <dt class="optional">
67
- payload.path <span class="property-type">string</span>
193
+ payload.from <span class="property-type">string</span>
68
194
  </dt>
69
- <dd>The path to the Modelfile to use for creating the model.</dd>
195
+ <dd>The base model to derive from.</dd>
70
196
 
71
197
  <dt class="optional">
72
- payload.modelfile <span class="property-type">string</span>
198
+ payload.stream <span class="property-type">boolean</span>
73
199
  </dt>
74
- <dd>The content of the Modelfile to create.</dd>
200
+ <dd>When true an <code>AsyncGenerator</code> is returned.</dd>
75
201
 
76
202
  <dt class="optional">
77
- 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>
78
234
  </dt>
79
- <dd>When true an AsyncGenerator is returned.</dd>
235
+ <dd>A key-value map of LoRA adapter configurations.</dd>
80
236
  </dl>
81
237
  </dd>
82
238
  </dl>
@@ -3,34 +3,178 @@ module.exports = function( RED ) {
3
3
 
4
4
  function OllamaCreateNode( config ) {
5
5
  RED.nodes.createNode( this, config )
6
- const node = this
7
6
 
8
- node.on( 'input', async function( msg ) {
9
- const {
10
- host: payloadHost,
11
- model: payloadModel,
12
- modelfile,
13
- stream,
14
- path
15
- } = msg.payload
7
+ this.modelType = config.modelType || 'str'
8
+ this.fromType = config.from || 'str'
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'
17
+ this.pathType = config.pathType || 'str'
16
18
 
19
+ const node = this
20
+ node.on( 'input', async function( msg ) {
17
21
  const server = RED.nodes.getNode( config.server )
18
- const host = ( server ) ? server.host + ':' + server.port : payloadHost
22
+ const host = msg?.payload?.host || ( server ) ? server.host + ':' + server.port : null
19
23
 
20
24
  const ollama = new Ollama( { host } )
21
25
 
22
- const modelConfig = RED.nodes.getNode( config.model )
23
- const model = ( modelConfig ) ? modelConfig.name : payloadModel
26
+ let model = null
27
+ if( msg?.payload?.model ) {
28
+ model = msg?.payload?.model
29
+ } else if( !!config.model ) {
30
+ if( node.modelType === 'str' ) {
31
+ model = config.model
32
+ } else if( node.modelType === 'msg' ) {
33
+ model = msg[ config.model ]
34
+ } else if( node.modelType === 'flow' ) {
35
+ model = node.context().flow.get( config.model )
36
+ } else if( node.modelType === 'global' ) {
37
+ model = node.context().global.get( config.model )
38
+ }
39
+ }
40
+
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
+ }
55
+
56
+ const stream = ( msg?.payload?.stream !== undefined ) ? msg?.payload?.stream : node.stream
57
+
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 )
160
+ }
161
+ }
24
162
 
25
163
  const response = await ollama.create( {
26
- model,
27
- modelfile,
28
- stream,
29
- path
30
- } )
31
- .catch( error => {
32
- node.error( error )
33
- } )
164
+ model,
165
+ from,
166
+ stream,
167
+ quantize,
168
+ template,
169
+ license,
170
+ system,
171
+ parameters,
172
+ messages,
173
+ adapters
174
+ } )
175
+ .catch( error => {
176
+ node.error( error )
177
+ } )
34
178
 
35
179
  msg.payload = response
36
180
  node.send( msg )
@@ -5,7 +5,8 @@
5
5
  defaults: {
6
6
  name: { value: 'Ollama Delete' },
7
7
  server: { value: '', type: 'ollama-config-server', required: false },
8
- model: { value: '', type: 'ollama-config-model', required: false },
8
+ model: { value: '', required: false },
9
+ modelType: { value: 'str', required: false },
9
10
  },
10
11
  inputs: 1,
11
12
  outputs: 1,
@@ -13,6 +14,17 @@
13
14
  label: function() {
14
15
  return this.name || 'ollama-delete';
15
16
  },
17
+ oneditprepare: function() {
18
+ if( !this.modelType ) {
19
+ this.modelType = 'str'
20
+ }
21
+
22
+ $( '#node-input-model' ).typedInput( {
23
+ type: 'str',
24
+ types: [ 'str', 'msg', 'flow', 'global' ],
25
+ typeField: '#node-input-modelType'
26
+ } )
27
+ }
16
28
  } )
17
29
  </script>
18
30
 
@@ -28,8 +40,9 @@
28
40
  </div>
29
41
 
30
42
  <div class="form-row">
31
- <label for="node-input-model"><i class="fa fa-bookmark"></i> Model</label>
43
+ <label for="node-input-model"><i class="fa fa-comment"></i> Model</label>
32
44
  <input type="text" id="node-input-model" />
45
+ <input type="hidden" id="node-input-modelType" />
33
46
  </div>
34
47
  </script>
35
48
 
@@ -44,24 +57,19 @@
44
57
  </dt>
45
58
 
46
59
  <dd>
47
- The payload object.
60
+ The payload object is an alternative way to configure the node.<br />
61
+ Any properties in the payload object will override the node configuration.
48
62
 
49
63
  <dl class="message-properties">
50
64
  <dt class="optional">
51
65
  payload.host <span class="property-type">string</span>
52
66
  </dt>
53
- <dd>
54
- The host of the Ollama instance.<br />
55
- <em>Alternative to the <code>Server</code> field.</em>
56
- </dd>
67
+ <dd>The host of the Ollama instance.</dd>
57
68
 
58
69
  <dt class="optional">
59
70
  payload.model <span class="property-type">string</span>
60
71
  </dt>
61
- <dd>
62
- The name of the model to delete.<br />
63
- <em>Alternative to the <code>Model</code> field.</em>
64
- </dd>
72
+ <dd>The name of the model to delete.</dd>
65
73
  </dl>
66
74
  </dd>
67
75
  </dl>
@@ -3,21 +3,30 @@ module.exports = function( RED ) {
3
3
 
4
4
  function OllamaDeleteNode( config ) {
5
5
  RED.nodes.createNode( this, config )
6
- const node = this
7
6
 
8
- node.on( 'input', async function( msg ) {
9
- const {
10
- host: payloadHost,
11
- model: payloadModel
12
- } = msg.payload
7
+ this.modelType = config.modelType || 'str'
13
8
 
9
+ const node = this
10
+ node.on( 'input', async function( msg ) {
14
11
  const server = RED.nodes.getNode( config.server )
15
- const host = ( server ) ? server.host + ':' + server.port : payloadHost
12
+ const host = msg?.payload?.host || ( server ) ? server.host + ':' + server.port : null
16
13
 
17
14
  const ollama = new Ollama( { host } )
18
15
 
19
- const modelConfig = RED.nodes.getNode( config.model )
20
- const model = ( modelConfig ) ? modelConfig.name : payloadModel
16
+ let model = null
17
+ if( msg?.payload?.model ) {
18
+ model = msg?.payload?.model
19
+ } else if( !!config.model ) {
20
+ if( node.modelType === 'str' ) {
21
+ model = config.model
22
+ } else if( node.modelType === 'msg' ) {
23
+ model = msg[ config.model ]
24
+ } else if( node.modelType === 'flow' ) {
25
+ model = node.context().flow.get( config.model )
26
+ } else if( node.modelType === 'global' ) {
27
+ model = node.context().global.get( config.model )
28
+ }
29
+ }
21
30
 
22
31
  const response = await ollama.delete( { model } )
23
32
  .catch( error => {