1use crate::ctx::Context;
2use crate::dbs::{Options, Transaction};
3use crate::doc::CursorDoc;
4use crate::err::Error;
5use crate::value::Value;
6use derive::Store;
7use revision::revisioned;
8use serde::{Deserialize, Serialize};
9use std::fmt;
10
11#[cfg(feature = "ml")]
12use crate::iam::Action;
13#[cfg(feature = "ml")]
14use crate::Permission;
15#[cfg(feature = "ml")]
16use futures::future::try_join_all;
17#[cfg(feature = "ml")]
18use std::collections::HashMap;
19#[cfg(feature = "ml")]
20use surrealml_core::execution::compute::ModelComputation;
21#[cfg(feature = "ml")]
22use surrealml_core::storage::surml_file::SurMlFile;
23
24#[cfg(feature = "ml")]
25const ARGUMENTS: &str = "The model expects 1 argument. The argument can be either a number, an object, or an array of numbers.";
26
27#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Serialize, Deserialize, Store, Hash)]
28#[revisioned(revision = 1)]
29pub struct Model {
30 pub name: String,
31 pub version: String,
32 pub args: Vec<Value>,
33}
34
35impl fmt::Display for Model {
36 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
37 write!(f, "ml::{}<{}>(", self.name, self.version)?;
38 for (idx, p) in self.args.iter().enumerate() {
39 if idx != 0 {
40 write!(f, ",")?;
41 }
42 write!(f, "{}", p)?;
43 }
44 write!(f, ")")
45 }
46}
47
48impl Model {
49 #[cfg(feature = "ml")]
50 pub(crate) async fn compute(
51 &self,
52 ctx: &Context<'_>,
53 opt: &Options,
54 txn: &Transaction,
55 doc: Option<&CursorDoc<'_>>,
56 ) -> Result<Value, Error> {
57 let opt = &opt.new_with_futures(true);
59 let name = format!("ml::{}", self.name);
61 ctx.check_allowed_function(name.as_str())?;
63 let val = {
65 let mut run = txn.lock().await;
67 run.get_and_cache_db_model(opt.ns(), opt.db(), &self.name, &self.version).await?
69 };
70 let path = format!(
72 "ml/{}/{}/{}-{}-{}.surml",
73 opt.ns(),
74 opt.db(),
75 self.name,
76 self.version,
77 val.hash
78 );
79 if opt.check_perms(Action::View) {
81 match &val.permissions {
82 Permission::Full => (),
83 Permission::None => {
84 return Err(Error::FunctionPermissions {
85 name: self.name.to_owned(),
86 })
87 }
88 Permission::Specific(e) => {
89 let opt = &opt.new_with_perms(false);
91 if !e.compute(ctx, opt, txn, doc).await?.is_truthy() {
93 return Err(Error::FunctionPermissions {
94 name: self.name.to_owned(),
95 });
96 }
97 }
98 }
99 }
100 let mut args =
102 try_join_all(self.args.iter().map(|v| v.compute(ctx, opt, txn, doc))).await?;
103 if args.len() != 1 {
105 return Err(Error::InvalidArguments {
106 name: format!("ml::{}<{}>", self.name, self.version),
107 message: ARGUMENTS.into(),
108 });
109 }
110 match args.swap_remove(0) {
112 Value::Object(v) => {
114 let mut args = v
116 .into_iter()
117 .map(|(k, v)| Ok((k, Value::try_into(v)?)))
118 .collect::<Result<HashMap<String, f32>, Error>>()
119 .map_err(|_| Error::InvalidArguments {
120 name: format!("ml::{}<{}>", self.name, self.version),
121 message: ARGUMENTS.into(),
122 })?;
123 let bytes = crate::obs::get(&path).await?;
125 let outcome = tokio::task::spawn_blocking(move || {
127 let mut file = SurMlFile::from_bytes(bytes).unwrap();
128 let compute_unit = ModelComputation {
129 surml_file: &mut file,
130 };
131 compute_unit.buffered_compute(&mut args).map_err(Error::ModelComputation)
132 })
133 .await
134 .unwrap()?;
135 Ok(outcome[0].into())
137 }
138 Value::Number(v) => {
140 let args: f32 = v.try_into().map_err(|_| Error::InvalidArguments {
142 name: format!("ml::{}<{}>", self.name, self.version),
143 message: ARGUMENTS.into(),
144 })?;
145 let bytes = crate::obs::get(&path).await?;
147 let tensor = ndarray::arr1::<f32>(&[args]).into_dyn();
149 let outcome = tokio::task::spawn_blocking(move || {
151 let mut file = SurMlFile::from_bytes(bytes).unwrap();
152 let compute_unit = ModelComputation {
153 surml_file: &mut file,
154 };
155 compute_unit.raw_compute(tensor, None).map_err(Error::ModelComputation)
156 })
157 .await
158 .unwrap()?;
159 Ok(outcome[0].into())
161 }
162 Value::Array(v) => {
164 let args = v
166 .into_iter()
167 .map(Value::try_into)
168 .collect::<Result<Vec<f32>, Error>>()
169 .map_err(|_| Error::InvalidArguments {
170 name: format!("ml::{}<{}>", self.name, self.version),
171 message: ARGUMENTS.into(),
172 })?;
173 let bytes = crate::obs::get(&path).await?;
175 let tensor = ndarray::arr1::<f32>(&args).into_dyn();
177 let outcome = tokio::task::spawn_blocking(move || {
179 let mut file = SurMlFile::from_bytes(bytes).unwrap();
180 let compute_unit = ModelComputation {
181 surml_file: &mut file,
182 };
183 compute_unit.raw_compute(tensor, None).map_err(Error::ModelComputation)
184 })
185 .await
186 .unwrap()?;
187 Ok(outcome[0].into())
189 }
190 _ => Err(Error::InvalidArguments {
192 name: format!("ml::{}<{}>", self.name, self.version),
193 message: ARGUMENTS.into(),
194 }),
195 }
196 }
197
198 #[cfg(not(feature = "ml"))]
199 pub(crate) async fn compute(
200 &self,
201 _ctx: &Context<'_>,
202 _opt: &Options,
203 _txn: &Transaction,
204 _doc: Option<&CursorDoc<'_>>,
205 ) -> Result<Value, Error> {
206 Err(Error::InvalidModel {
207 message: String::from("Machine learning computation is not enabled."),
208 })
209 }
210}