Skip to main content

hanzo_client/apis/
ml_api.rs

1/*
2 * Hanzo Cloud API
3 *
4 * The Hanzo Cloud API as a customer calls it: every operation under /v1/ except the operator's admin product, relay routes, legacy spellings and capabilities still reached by flag. Tagged by product: the first path segment after /v1/.
5 *
6 * The version of the OpenAPI document: v1
7 * 
8 * Generated by: https://openapi-generator.tech
9 */
10
11
12use reqwest;
13use serde::{Deserialize, Serialize, de::Error as _};
14use crate::{apis::ResponseContent, models};
15use super::{Error, configuration, ContentType};
16
17
18/// struct for typed errors of method [`delete_ml_models_by_name`]
19#[derive(Debug, Clone, Serialize, Deserialize)]
20#[serde(untagged)]
21pub enum DeleteMlModelsByNameError {
22    UnknownValue(serde_json::Value),
23}
24
25/// struct for typed errors of method [`get_ml_health`]
26#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(untagged)]
28pub enum GetMlHealthError {
29    UnknownValue(serde_json::Value),
30}
31
32/// struct for typed errors of method [`get_ml_models`]
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum GetMlModelsError {
36    UnknownValue(serde_json::Value),
37}
38
39/// struct for typed errors of method [`get_ml_models_by_name`]
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum GetMlModelsByNameError {
43    UnknownValue(serde_json::Value),
44}
45
46/// struct for typed errors of method [`patch_ml_models_by_name`]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48#[serde(untagged)]
49pub enum PatchMlModelsByNameError {
50    UnknownValue(serde_json::Value),
51}
52
53/// struct for typed errors of method [`post_ml_models`]
54#[derive(Debug, Clone, Serialize, Deserialize)]
55#[serde(untagged)]
56pub enum PostMlModelsError {
57    UnknownValue(serde_json::Value),
58}
59
60/// struct for typed errors of method [`post_ml_models_by_name_predict`]
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(untagged)]
63pub enum PostMlModelsByNamePredictError {
64    UnknownValue(serde_json::Value),
65}
66
67
68/// Deletes a deployed inference model. kserve owns the teardown: the InferenceService goes away and the serving deployment behind it follows, so the model stops answering predict calls. Answers 204, or 404 for a name the caller's org does not own.
69pub async fn delete_ml_models_by_name(configuration: &configuration::Configuration, name: &str) -> Result<(), Error<DeleteMlModelsByNameError>> {
70    // add a prefix to parameters to efficiently prevent name collisions
71    let p_name = name;
72
73    let uri_str = format!("{}/v1/ml/models/{name}", configuration.base_path, name=crate::apis::urlencode(p_name));
74    let mut req_builder = configuration.client.request(reqwest::Method::DELETE, &uri_str);
75
76    if let Some(ref user_agent) = configuration.user_agent {
77        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
78    }
79    if let Some(ref token) = configuration.bearer_access_token {
80        req_builder = req_builder.bearer_auth(token.to_owned());
81    };
82
83    let req = req_builder.build()?;
84    let resp = configuration.client.execute(req).await?;
85
86    let status = resp.status();
87
88    if !status.is_client_error() && !status.is_server_error() {
89        Ok(())
90    } else {
91        let content = resp.text().await?;
92        let entity: Option<DeleteMlModelsByNameError> = serde_json::from_str(&content).ok();
93        Err(Error::ResponseError(ResponseContent { status, content, entity }))
94    }
95}
96
97/// Reports whether the model-serving plane is genuinely usable: that the Kubernetes API answers, that the InferenceService CRD is actually served by this cluster, and that the cluster holds at least one serving runtime to run a model ON. It is a REAL probe, not status theatre — it makes a live call rather than reporting a flag set at boot.  200 only when everything checks out. Otherwise 503 CARRYING THE REPORT — which component failed, and the real error — and that body is the reason this is not a typed op: a typed op reaches a non-2xx by returning an error, and the envelope that produces would drop exactly the detail the probe exists to deliver.  The runtime count is reported as its own field and is a SEPARATE fact from the CRD being served: a cluster with the CRD but no runtime accepts a deploy and then never schedules it, so reporting only the CRD would answer 200 while every model hangs. A runtime list this service cannot read reports the read error instead of a count, because a missing grant is a broken probe and not an empty cluster.  It answers about the cluster, not about a tenant, so it takes no org and reveals no tenant data. A cluster with no kserve CRD reports degraded honestly rather than failing later at the first deploy.
98pub async fn get_ml_health(configuration: &configuration::Configuration, ) -> Result<(), Error<GetMlHealthError>> {
99
100    let uri_str = format!("{}/v1/ml/health", configuration.base_path);
101    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
102
103    if let Some(ref user_agent) = configuration.user_agent {
104        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
105    }
106    if let Some(ref token) = configuration.bearer_access_token {
107        req_builder = req_builder.bearer_auth(token.to_owned());
108    };
109
110    let req = req_builder.build()?;
111    let resp = configuration.client.execute(req).await?;
112
113    let status = resp.status();
114
115    if !status.is_client_error() && !status.is_server_error() {
116        Ok(())
117    } else {
118        let content = resp.text().await?;
119        let entity: Option<GetMlHealthError> = serde_json::from_str(&content).ok();
120        Err(Error::ResponseError(ResponseContent { status, content, entity }))
121    }
122}
123
124/// Lists the inference models deployed in the caller's org. Each entry carries the model's name, when Kubernetes admitted it, and kserve's live status — the spec is on the single-model read. An org that has deployed nothing gets an empty list.
125pub async fn get_ml_models(configuration: &configuration::Configuration, ) -> Result<models::MlResourceList, Error<GetMlModelsError>> {
126
127    let uri_str = format!("{}/v1/ml/models", configuration.base_path);
128    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
129
130    if let Some(ref user_agent) = configuration.user_agent {
131        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
132    }
133    if let Some(ref token) = configuration.bearer_access_token {
134        req_builder = req_builder.bearer_auth(token.to_owned());
135    };
136
137    let req = req_builder.build()?;
138    let resp = configuration.client.execute(req).await?;
139
140    let status = resp.status();
141    let content_type = resp
142        .headers()
143        .get("content-type")
144        .and_then(|v| v.to_str().ok())
145        .unwrap_or("application/octet-stream");
146    let content_type = super::ContentType::from(content_type);
147
148    if !status.is_client_error() && !status.is_server_error() {
149        let content = resp.text().await?;
150        match content_type {
151            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
152            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::MlResourceList`"))),
153            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::MlResourceList`")))),
154        }
155    } else {
156        let content = resp.text().await?;
157        let entity: Option<GetMlModelsError> = serde_json::from_str(&content).ok();
158        Err(Error::ResponseError(ResponseContent { status, content, entity }))
159    }
160}
161
162/// Returns one deployed inference model. Its spec comes with it, and kserve's live status, which is where readiness and the serving address appear. A name the caller's org does not own answers 404, exactly as an unknown name does, so a probe learns nothing about another tenant's models.
163pub async fn get_ml_models_by_name(configuration: &configuration::Configuration, name: &str) -> Result<models::MlResource, Error<GetMlModelsByNameError>> {
164    // add a prefix to parameters to efficiently prevent name collisions
165    let p_name = name;
166
167    let uri_str = format!("{}/v1/ml/models/{name}", configuration.base_path, name=crate::apis::urlencode(p_name));
168    let mut req_builder = configuration.client.request(reqwest::Method::GET, &uri_str);
169
170    if let Some(ref user_agent) = configuration.user_agent {
171        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
172    }
173    if let Some(ref token) = configuration.bearer_access_token {
174        req_builder = req_builder.bearer_auth(token.to_owned());
175    };
176
177    let req = req_builder.build()?;
178    let resp = configuration.client.execute(req).await?;
179
180    let status = resp.status();
181    let content_type = resp
182        .headers()
183        .get("content-type")
184        .and_then(|v| v.to_str().ok())
185        .unwrap_or("application/octet-stream");
186    let content_type = super::ContentType::from(content_type);
187
188    if !status.is_client_error() && !status.is_server_error() {
189        let content = resp.text().await?;
190        match content_type {
191            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
192            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::MlResource`"))),
193            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::MlResource`")))),
194        }
195    } else {
196        let content = resp.text().await?;
197        let entity: Option<GetMlModelsByNameError> = serde_json::from_str(&content).ok();
198        Err(Error::ResponseError(ResponseContent { status, content, entity }))
199    }
200}
201
202/// Applies a JSON merge patch to one of the caller org's deployed models and answers the updated resource — the way to change a model's image, replica count or resource requests without tearing the deployment down.  The body is relayed to Kubernetes VERBATIM. That is deliberate and it is why this route is not a typed op: re-encoding a merge patch changes what it means, because an integer that round-trips through a generic decoder comes back a float. Merge-patch semantics apply as written — a null removes a field, and a list is replaced whole rather than merged.  Scoped to the caller's own tenant namespace, resolved from the validated org and project; a name the caller's tenant does not hold is a 404, never another tenant's resource. An empty body is refused, and a patch Kubernetes rejects comes back 422 with its reason rather than being silently dropped.
203pub async fn patch_ml_models_by_name(configuration: &configuration::Configuration, name: &str) -> Result<(), Error<PatchMlModelsByNameError>> {
204    // add a prefix to parameters to efficiently prevent name collisions
205    let p_name = name;
206
207    let uri_str = format!("{}/v1/ml/models/{name}", configuration.base_path, name=crate::apis::urlencode(p_name));
208    let mut req_builder = configuration.client.request(reqwest::Method::PATCH, &uri_str);
209
210    if let Some(ref user_agent) = configuration.user_agent {
211        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
212    }
213    if let Some(ref token) = configuration.bearer_access_token {
214        req_builder = req_builder.bearer_auth(token.to_owned());
215    };
216
217    let req = req_builder.build()?;
218    let resp = configuration.client.execute(req).await?;
219
220    let status = resp.status();
221
222    if !status.is_client_error() && !status.is_server_error() {
223        Ok(())
224    } else {
225        let content = resp.text().await?;
226        let entity: Option<PatchMlModelsByNameError> = serde_json::from_str(&content).ok();
227        Err(Error::ResponseError(ResponseContent { status, content, entity }))
228    }
229}
230
231/// Deploys one inference model for the caller's org, and answers 201 with the model as Kubernetes admitted it.  The `spec` is a kserve InferenceService spec, passed through unchanged — this plane owns the tenancy, the billing and the namespace, and kserve owns what a model IS. An unfunded org is refused BEFORE anything is created, so nobody runs free GPU compute and nobody is charged for a resource that was never made.
232pub async fn post_ml_models(configuration: &configuration::Configuration, ml_create: models::MlCreate) -> Result<models::MlResource, Error<PostMlModelsError>> {
233    // add a prefix to parameters to efficiently prevent name collisions
234    let p_ml_create = ml_create;
235
236    let uri_str = format!("{}/v1/ml/models", configuration.base_path);
237    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
238
239    if let Some(ref user_agent) = configuration.user_agent {
240        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
241    }
242    if let Some(ref token) = configuration.bearer_access_token {
243        req_builder = req_builder.bearer_auth(token.to_owned());
244    };
245    req_builder = req_builder.json(&p_ml_create);
246
247    let req = req_builder.build()?;
248    let resp = configuration.client.execute(req).await?;
249
250    let status = resp.status();
251    let content_type = resp
252        .headers()
253        .get("content-type")
254        .and_then(|v| v.to_str().ok())
255        .unwrap_or("application/octet-stream");
256    let content_type = super::ContentType::from(content_type);
257
258    if !status.is_client_error() && !status.is_server_error() {
259        let content = resp.text().await?;
260        match content_type {
261            ContentType::Json => serde_json::from_str(&content).map_err(Error::from),
262            ContentType::Text => return Err(Error::from(serde_json::Error::custom("Received `text/plain` content type response that cannot be converted to `models::MlResource`"))),
263            ContentType::Unsupported(unknown_type) => return Err(Error::from(serde_json::Error::custom(format!("Received `{unknown_type}` content type response that cannot be converted to `models::MlResource`")))),
264        }
265    } else {
266        let content = resp.text().await?;
267        let entity: Option<PostMlModelsError> = serde_json::from_str(&content).ok();
268        Err(Error::ResponseError(ResponseContent { status, content, entity }))
269    }
270}
271
272/// Sends the request body to the named model's predictor and answers the predictor's reply — its status code, its body bytes and its Content-Type, all unchanged. This is the inference call itself, not a description of one.  VERBATIM IS THE CONTRACT, and it is why this route is not a typed op: a model-side error has to surface as the model's own error, not as this layer's paraphrase of it. The body shape is the kserve v2 inference protocol's, which means the runtime decides it, not this API. The v2 model name defaults to the resource name — kserve's single-model convention — and a multi-model runtime selects one with the `model` query parameter.  A model that exists but has no serving address yet answers 503 'not ready' rather than a confusing connection error: deployed is not the same as serving. Scoped to the caller's own tenant namespace from the validated org and project, so a name another tenant owns is simply a 404. The predictor's response body is read up to a fixed ceiling.
273pub async fn post_ml_models_by_name_predict(configuration: &configuration::Configuration, name: &str) -> Result<(), Error<PostMlModelsByNamePredictError>> {
274    // add a prefix to parameters to efficiently prevent name collisions
275    let p_name = name;
276
277    let uri_str = format!("{}/v1/ml/models/{name}/predict", configuration.base_path, name=crate::apis::urlencode(p_name));
278    let mut req_builder = configuration.client.request(reqwest::Method::POST, &uri_str);
279
280    if let Some(ref user_agent) = configuration.user_agent {
281        req_builder = req_builder.header(reqwest::header::USER_AGENT, user_agent.clone());
282    }
283    if let Some(ref token) = configuration.bearer_access_token {
284        req_builder = req_builder.bearer_auth(token.to_owned());
285    };
286
287    let req = req_builder.build()?;
288    let resp = configuration.client.execute(req).await?;
289
290    let status = resp.status();
291
292    if !status.is_client_error() && !status.is_server_error() {
293        Ok(())
294    } else {
295        let content = resp.text().await?;
296        let entity: Option<PostMlModelsByNamePredictError> = serde_json::from_str(&content).ok();
297        Err(Error::ResponseError(ResponseContent { status, content, entity }))
298    }
299}
300