zai_rs/knowledge/
update.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use validator::Validate;
5
6use super::create::{BackgroundColor, EmbeddingId, KnowledgeIcon};
7use crate::client::http::HttpClient;
8
9/// Update body for editing a knowledge base
10#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
11pub struct UpdateKnowledgeBody {
12    /// Embedding model id (3 or 11)
13    #[serde(skip_serializing_if = "Option::is_none")]
14    pub embedding_id: Option<EmbeddingId>,
15    /// Knowledge base name
16    #[serde(skip_serializing_if = "Option::is_none")]
17    #[validate(length(min = 1))]
18    pub name: Option<String>,
19    /// Knowledge base description
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub description: Option<String>,
22    /// Background color
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub background: Option<BackgroundColor>,
25    /// Icon name
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub icon: Option<KnowledgeIcon>,
28    /// Callback URL when rebuilding is required
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub callback_url: Option<String>,
31    /// Callback headers as key-value
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub callback_header: Option<HashMap<String, String>>,
34}
35
36impl UpdateKnowledgeBody {
37    /// Returns true if no fields are set
38    fn is_empty(&self) -> bool {
39        self.embedding_id.is_none()
40            && self.name.is_none()
41            && self.description.is_none()
42            && self.background.is_none()
43            && self.icon.is_none()
44            && self.callback_url.is_none()
45            && self.callback_header.is_none()
46    }
47}
48
49/// Knowledge update request (PUT /llm-application/open/knowledge/{id})
50pub struct KnowledgeUpdateRequest {
51    /// Bearer API key
52    pub key: String,
53    url: String,
54    body: UpdateKnowledgeBody,
55}
56
57impl KnowledgeUpdateRequest {
58    /// Build update request targeting a specific id with empty body
59    pub fn new(key: String, id: impl AsRef<str>) -> Self {
60        let url = format!(
61            "https://open.bigmodel.cn/api/llm-application/open/knowledge/{}",
62            id.as_ref()
63        );
64        Self {
65            key,
66            url,
67            body: UpdateKnowledgeBody::default(),
68        }
69    }
70
71    /// Setters to update individual fields
72    pub fn with_embedding_id(mut self, id: EmbeddingId) -> Self {
73        self.body.embedding_id = Some(id);
74        self
75    }
76    pub fn with_name(mut self, name: impl Into<String>) -> Self {
77        self.body.name = Some(name.into());
78        self
79    }
80    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
81        self.body.description = Some(desc.into());
82        self
83    }
84    pub fn with_background(mut self, bg: BackgroundColor) -> Self {
85        self.body.background = Some(bg);
86        self
87    }
88    pub fn with_icon(mut self, icon: KnowledgeIcon) -> Self {
89        self.body.icon = Some(icon);
90        self
91    }
92    pub fn with_callback_url(mut self, url: impl Into<String>) -> Self {
93        self.body.callback_url = Some(url.into());
94        self
95    }
96    pub fn with_callback_header(mut self, headers: HashMap<String, String>) -> Self {
97        self.body.callback_header = Some(headers);
98        self
99    }
100
101    /// Validate and send the update request. Requires at least one field set.
102    pub async fn send(&self) -> anyhow::Result<KnowledgeUpdateResponse> {
103        if self.body.is_empty() {
104            return Err(anyhow::anyhow!(
105                "update body is empty; set at least one field"
106            ));
107        }
108        self.body.validate()?;
109        let resp = self.put().await?;
110        let parsed = resp.json::<KnowledgeUpdateResponse>().await?;
111        Ok(parsed)
112    }
113
114    /// Perform HTTP PUT with JSON body and error handling similar to other clients
115    pub fn put(
116        &self,
117    ) -> impl std::future::Future<Output = anyhow::Result<reqwest::Response>> + Send {
118        let url = self.url.clone();
119        let key = self.key.clone();
120        let body = self.body.clone();
121        async move {
122            let body_str = serde_json::to_string(&body)?;
123            let resp = reqwest::Client::new()
124                .put(url)
125                .bearer_auth(key)
126                .header("Content-Type", "application/json")
127                .body(body_str)
128                .send()
129                .await?;
130            let status = resp.status();
131            if status.is_success() {
132                return Ok(resp);
133            }
134            // Non-success: try parse standard error envelope {"error": {code, message}}
135            let text = resp.text().await.unwrap_or_default();
136            #[derive(serde::Deserialize)]
137            struct ErrEnv {
138                error: ErrObj,
139            }
140            #[derive(serde::Deserialize)]
141            struct ErrObj {
142                code: serde_json::Value,
143                message: String,
144            }
145            if let Ok(parsed) = serde_json::from_str::<ErrEnv>(&text) {
146                return Err(anyhow::anyhow!(
147                    "HTTP {} {} | code={} | message={}",
148                    status.as_u16(),
149                    status.canonical_reason().unwrap_or(""),
150                    parsed.error.code,
151                    parsed.error.message
152                ));
153            }
154            Err(anyhow::anyhow!(
155                "HTTP {} {} | body={}",
156                status.as_u16(),
157                status.canonical_reason().unwrap_or(""),
158                text
159            ))
160        }
161    }
162}
163
164impl HttpClient for KnowledgeUpdateRequest {
165    type Body = UpdateKnowledgeBody;
166    type ApiUrl = String;
167    type ApiKey = String;
168
169    fn api_url(&self) -> &Self::ApiUrl {
170        &self.url
171    }
172    fn api_key(&self) -> &Self::ApiKey {
173        &self.key
174    }
175    fn body(&self) -> &Self::Body {
176        &self.body
177    }
178}
179
180/// Update response envelope without data
181#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
182pub struct KnowledgeUpdateResponse {
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub code: Option<i64>,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub message: Option<String>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub timestamp: Option<u64>,
189}