1
- use ollama_workflows:: ollama_rs:: Ollama ;
1
+ use std:: time:: Duration ;
2
+
3
+ use ollama_workflows:: { ollama_rs:: Ollama , Executor , Model , ProgramMemory , Workflow } ;
2
4
3
5
const DEFAULT_OLLAMA_HOST : & str = "http://127.0.0.1" ;
4
6
const DEFAULT_OLLAMA_PORT : u16 = 11434 ;
@@ -46,12 +48,11 @@ impl OllamaConfig {
46
48
. unwrap_or ( DEFAULT_OLLAMA_PORT ) ;
47
49
48
50
// Ollama workflows may require specific models to be loaded regardless of the choices
49
- let hardcoded_models = HARDCODED_MODELS
50
- . into_iter ( )
51
- . map ( |s| s. to_string ( ) )
52
- . collect ( ) ;
51
+ let hardcoded_models = HARDCODED_MODELS . iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ;
53
52
54
- let auto_pull = std:: env:: var ( "OLLAMA_AUTO_PULL" ) . unwrap_or_default ( ) == "true" ;
53
+ let auto_pull = std:: env:: var ( "OLLAMA_AUTO_PULL" )
54
+ . map ( |s| s == "true" )
55
+ . unwrap_or_default ( ) ;
55
56
56
57
Self {
57
58
host,
@@ -61,19 +62,20 @@ impl OllamaConfig {
61
62
}
62
63
}
63
64
64
- /// Check if requested models exist.
65
- pub async fn check ( & self , external_models : Vec < String > ) -> Result < ( ) , String > {
65
+ /// Check if requested models exist in Ollama, and then tests them using a workflow.
66
+ pub async fn check (
67
+ & self ,
68
+ external_models : Vec < Model > ,
69
+ test_workflow_timeout : Duration ,
70
+ ) -> Result < Vec < Model > , String > {
66
71
log:: info!(
67
- "Checking Ollama requirements (auto-pull {})" ,
68
- if self . auto_pull { "on" } else { "off" }
72
+ "Checking Ollama requirements (auto-pull {}, workflow timeout: {}s)" ,
73
+ if self . auto_pull { "on" } else { "off" } ,
74
+ test_workflow_timeout. as_secs( )
69
75
) ;
70
76
71
77
let ollama = Ollama :: new ( & self . host , self . port ) ;
72
78
73
- // the list of required models is those given in DKN_MODELS and the hardcoded ones
74
- let mut required_models = self . hardcoded_models . clone ( ) ;
75
- required_models. extend ( external_models) ;
76
-
77
79
// fetch local models
78
80
let local_models = match ollama. list_local_models ( ) . await {
79
81
Ok ( models) => models. into_iter ( ) . map ( |m| m. name ) . collect :: < Vec < _ > > ( ) ,
@@ -84,34 +86,139 @@ impl OllamaConfig {
84
86
}
85
87
}
86
88
} ;
89
+ log:: info!( "Found local Ollama models: {:#?}" , local_models) ;
90
+
91
+ // check hardcoded models & pull them if available
92
+ // these are not used directly by the user, but are needed for the workflows
93
+ log:: debug!( "Checking hardcoded models: {:#?}" , self . hardcoded_models) ;
94
+ for model in & self . hardcoded_models {
95
+ if !local_models. contains ( model) {
96
+ self . try_pull ( & ollama, model. to_owned ( ) ) . await ?;
97
+ }
98
+
99
+ // we dont check workflows for hardcoded models
100
+ }
101
+
102
+ // check external models & pull them if available
103
+ // and also run a test workflow for them
104
+ let mut good_models = Vec :: new ( ) ;
105
+ for model in external_models {
106
+ if !local_models. contains ( & model. to_string ( ) ) {
107
+ self . try_pull ( & ollama, model. to_string ( ) ) . await ?;
108
+ }
109
+
110
+ let ok = self
111
+ . test_workflow ( model. clone ( ) , test_workflow_timeout)
112
+ . await ;
113
+ if ok {
114
+ good_models. push ( model) ;
115
+ }
116
+ }
117
+
118
+ log:: info!(
119
+ "Ollama checks are finished, using models: {:#?}" ,
120
+ good_models
121
+ ) ;
122
+ Ok ( good_models)
123
+ }
87
124
88
- // check that each required model exists here
89
- log:: debug!( "Checking required models: {:#?}" , required_models) ;
90
- log:: debug!( "Found local models: {:#?}" , local_models) ;
91
- for model in required_models {
92
- if !local_models. iter ( ) . any ( |m| * m == model) {
93
- log:: warn!( "Model {} not found in Ollama" , model) ;
94
- if self . auto_pull {
95
- // if auto-pull is enabled, pull the model
96
- log:: info!(
97
- "Downloading missing model {} (this may take a while)" ,
98
- model
99
- ) ;
100
- let status = ollama
101
- . pull_model ( model, false )
102
- . await
103
- . map_err ( |e| format ! ( "Error pulling model with Ollama: {}" , e) ) ?;
104
- log:: debug!( "Pulled model with Ollama, final status: {:#?}" , status) ;
125
+ /// Pulls a model if `auto_pull` exists, otherwise returns an error.
126
+ async fn try_pull ( & self , ollama : & Ollama , model : String ) -> Result < ( ) , String > {
127
+ log:: warn!( "Model {} not found in Ollama" , model) ;
128
+ if self . auto_pull {
129
+ // if auto-pull is enabled, pull the model
130
+ log:: info!(
131
+ "Downloading missing model {} (this may take a while)" ,
132
+ model
133
+ ) ;
134
+ let status = ollama
135
+ . pull_model ( model, false )
136
+ . await
137
+ . map_err ( |e| format ! ( "Error pulling model with Ollama: {}" , e) ) ?;
138
+ log:: debug!( "Pulled model with Ollama, final status: {:#?}" , status) ;
139
+ Ok ( ( ) )
140
+ } else {
141
+ // otherwise, give error
142
+ log:: error!( "Please download missing model with: ollama pull {}" , model) ;
143
+ log:: error!( "Or, set OLLAMA_AUTO_PULL=true to pull automatically." ) ;
144
+ return Err ( "Required model not pulled in Ollama." . into ( ) ) ;
145
+ }
146
+ }
147
+
148
+ /// Runs a small workflow to test Ollama Workflows.
149
+ ///
150
+ /// This is to see if a given system can execute Ollama workflows for their chosen models,
151
+ /// e.g. if they have enough RAM/CPU and such.
152
+ pub async fn test_workflow ( & self , model : Model , timeout : Duration ) -> bool {
153
+ // this is the test workflow that we will run
154
+ // TODO: when Workflow's have `Clone`, we can remove the repetitive parsing here
155
+ let workflow = serde_json:: from_value :: < Workflow > ( serde_json:: json!( {
156
+ "name" : "Simple" ,
157
+ "description" : "This is a simple workflow" ,
158
+ "config" : {
159
+ "max_steps" : 5 ,
160
+ "max_time" : 100 ,
161
+ "max_tokens" : 100 ,
162
+ "tools" : [ ]
163
+ } ,
164
+ "tasks" : [
165
+ {
166
+ "id" : "A" ,
167
+ "name" : "Random Poem" ,
168
+ "description" : "Writes a poem about Kapadokya." ,
169
+ "prompt" : "Please write a poem about Kapadokya." ,
170
+ "inputs" : [ ] ,
171
+ "operator" : "generation" ,
172
+ "outputs" : [
173
+ {
174
+ "type" : "write" ,
175
+ "key" : "poem" ,
176
+ "value" : "__result"
177
+ }
178
+ ]
179
+ } ,
180
+ {
181
+ "id" : "__end" ,
182
+ "name" : "end" ,
183
+ "description" : "End of the task" ,
184
+ "prompt" : "End of the task" ,
185
+ "inputs" : [ ] ,
186
+ "operator" : "end" ,
187
+ "outputs" : [ ]
188
+ }
189
+ ] ,
190
+ "steps" : [
191
+ {
192
+ "source" : "A" ,
193
+ "target" : "end"
194
+ }
195
+ ] ,
196
+ "return_value" : {
197
+ "input" : {
198
+ "type" : "read" ,
199
+ "key" : "poem"
200
+ }
201
+ }
202
+ } ) )
203
+ . expect ( "Preset workflow should be parsed" ) ;
204
+
205
+ log:: info!( "Testing model {}" , model) ;
206
+ let executor = Executor :: new_at ( model. clone ( ) , & self . host , self . port ) ;
207
+ let mut memory = ProgramMemory :: new ( ) ;
208
+ tokio:: select! {
209
+ _ = tokio:: time:: sleep( timeout) => {
210
+ log:: warn!( "Ignoring model {}: Timeout" , model) ;
211
+ } ,
212
+ result = executor. execute( None , workflow, & mut memory) => {
213
+ if result. is_empty( ) {
214
+ log:: warn!( "Ignoring model {}: Empty Result" , model) ;
105
215
} else {
106
- // otherwise, give error
107
- log:: error!( "Please download it with: ollama pull {}" , model) ;
108
- log:: error!( "Or, set OLLAMA_AUTO_PULL=true to pull automatically." ) ;
109
- return Err ( "Required model not pulled in Ollama." . into ( ) ) ;
216
+ log:: info!( "Accepting model {}" , model) ;
217
+ return true ;
110
218
}
111
219
}
112
- }
220
+ } ;
113
221
114
- log:: info!( "Ollama setup is all good." , ) ;
115
- Ok ( ( ) )
222
+ return false ;
116
223
}
117
224
}
0 commit comments